diff --git a/app/brave_generated_resources.grd b/app/brave_generated_resources.grd index 1d543eacaab9..1b67d64cd2e9 100644 --- a/app/brave_generated_resources.grd +++ b/app/brave_generated_resources.grd @@ -340,6 +340,9 @@ By installing this extension, you are agreeing to the Google Widevine Terms of U Open in Tor + + Open in IPFS + Onion Available @@ -742,6 +745,12 @@ By installing this extension, you are agreeing to the Google Widevine Terms of U Automatically uses the configured gateway for IPFS resolutions when an IPFS gateway resource is encountered. + + Automatically redirect to IPFS pages via DNSLink when possible + + + Automatically uses DNSLink to navigate to an IPFS version of a website when possible + Uses Hangouts component to enable screen sharing and other features in the browser. diff --git a/app/vector_icons/BUILD.gn b/app/vector_icons/BUILD.gn index 3df9560bfc4b..b08de417046a 100644 --- a/app/vector_icons/BUILD.gn +++ b/app/vector_icons/BUILD.gn @@ -12,6 +12,7 @@ aggregate_vector_icons("brave_vector_icons") { "brave_ads_close_button.icon", "brave_ads_info.icon", "download_unlock.icon", + "open_in_ipfs.icon", "open_in_tor.icon", "speedreader.icon", "speedreader_on_active.icon", diff --git a/app/vector_icons/open_in_ipfs.icon b/app/vector_icons/open_in_ipfs.icon new file mode 100644 index 000000000000..0c358687e4e3 --- /dev/null +++ b/app/vector_icons/open_in_ipfs.icon @@ -0,0 +1,38 @@ +// 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/. + +CANVAS_DIMENSIONS, 12, +MOVE_TO, 11.5f, 10, +R_CUBIC_TO, -0.28f, 0, -0.5f, -0.22f, -0.5f, -0.5f, +V_LINE_TO, 1, +H_LINE_TO, 2.5f, +CUBIC_TO, 2.22f, 1, 2, 0.78f, 2, 0.5f, +R_CUBIC_TO, 0, -0.28f, 0.22f, -0.5f, 0.5f, -0.5f, +H_LINE_TO, 11, +R_CUBIC_TO, 0.55f, 0, 1, 0.45f, 1, 1, +R_V_LINE_TO, 8.5f, +R_CUBIC_TO, 0, 0.28f, -0.22f, 0.5f, -0.5f, 0.5f, +CLOSE, +MOVE_TO, 10, 3, +R_V_LINE_TO, 8, +R_CUBIC_TO, 0, 0.55f, -0.45f, 1, -1, 1, +H_LINE_TO, 1, +R_CUBIC_TO, -0.55f, 0, -1, -0.45f, -1, -1, +V_LINE_TO, 3, +R_CUBIC_TO, 0, -0.55f, 0.45f, -1, 1, -1, +R_H_LINE_TO, 8, +R_CUBIC_TO, 0.55f, 0, 1, 0.45f, 1, 1, +CLOSE, +R_MOVE_TO, -9, 8, +R_H_LINE_TO, 8, +R_LINE_TO, 0, -6, +H_LINE_TO, 1, +R_V_LINE_TO, 6, +CLOSE, +R_MOVE_TO, 0, -7, +R_H_LINE_TO, 8, +V_LINE_TO, 3, +H_LINE_TO, 1, +R_V_LINE_TO, 1, +CLOSE diff --git a/browser/BUILD.gn b/browser/BUILD.gn index c94f107e8a45..71efd873af8c 100644 --- a/browser/BUILD.gn +++ b/browser/BUILD.gn @@ -299,6 +299,8 @@ source_set("browser_process") { sources += [ "ipfs/content_browser_client_helper.cc", "ipfs/content_browser_client_helper.h", + "ipfs/ipfs_host_resolver.cc", + "ipfs/ipfs_host_resolver.h", "ipfs/ipfs_service_factory.cc", "ipfs/ipfs_service_factory.h", "ipfs/ipfs_tab_helper.cc", diff --git a/browser/extensions/api/settings_private/brave_prefs_util.cc b/browser/extensions/api/settings_private/brave_prefs_util.cc index 2604ff65efb8..a673c9e2ef49 100644 --- a/browser/extensions/api/settings_private/brave_prefs_util.cc +++ b/browser/extensions/api/settings_private/brave_prefs_util.cc @@ -192,6 +192,8 @@ const PrefsUtil::TypedPrefMap& BravePrefsUtil::GetAllowlistedKeys() { settings_api::PrefType::PREF_TYPE_STRING; (*s_brave_allowlist)[kIPFSAutoRedirectGateway] = settings_api::PrefType::PREF_TYPE_BOOLEAN; + (*s_brave_allowlist)[kIPFSAutoRedirectDNSLink] = + settings_api::PrefType::PREF_TYPE_BOOLEAN; #endif // Media Router Pref (*s_brave_allowlist)[kBraveEnabledMediaRouter] = diff --git a/browser/ipfs/ipfs_host_resolver.cc b/browser/ipfs/ipfs_host_resolver.cc new file mode 100644 index 000000000000..43b084dee6a6 --- /dev/null +++ b/browser/ipfs/ipfs_host_resolver.cc @@ -0,0 +1,106 @@ +/* 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 +#include + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "brave/browser/ipfs/ipfs_host_resolver.h" +#include "chrome/browser/net/secure_dns_config.h" +#include "chrome/browser/net/system_network_context_manager.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "net/base/host_port_pair.h" +#include "net/dns/public/dns_protocol.h" + +namespace { + +// DNSLink values are of the form: dnslink= +// https://dnslink.io/#dnslink-format +const char kDnsLinkHeader[] = "dnslink"; + +// Expects dns TXT record in format: name=value +std::string GetDNSRecordValue(const std::vector& text_results, + const std::string& name) { + for (const auto& txt : text_results) { + std::vector tokens = base::SplitString( + txt, "=", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); + if (!tokens.size()) + continue; + if (tokens.front() != name) + continue; + return tokens.back(); + } + return std::string(); +} + +} // namespace + +namespace ipfs { + +IPFSHostResolver::IPFSHostResolver( + network::mojom::NetworkContext* network_context, + const std::string& prefix) + : prefix_(prefix), network_context_(network_context) { + DCHECK(network_context); +} +IPFSHostResolver::~IPFSHostResolver() {} + +void IPFSHostResolver::Resolve(const net::HostPortPair& host, + const net::NetworkIsolationKey& isolation_key, + net::DnsQueryType dns_query_type, + HostTextResultsCallback callback) { + if (!callback) + return; + + if (host.host() == resolving_host_) { + if (callback && has_dnslink_) { + std::move(callback).Run(host.host()); + } + return; + } + + network::mojom::ResolveHostParametersPtr parameters = + network::mojom::ResolveHostParameters::New(); + parameters->dns_query_type = dns_query_type; + + receiver_.reset(); + resolved_callback_ = std::move(callback); + has_dnslink_ = false; + resolving_host_ = host.host(); + net::HostPortPair local_host_port(prefix_ + resolving_host_, host.port()); + + network_context_->ResolveHost(local_host_port, isolation_key, + std::move(parameters), + receiver_.BindNewPipeAndPassRemote()); +} + +void IPFSHostResolver::OnComplete( + int result, + const net::ResolveErrorInfo& error_info, + const base::Optional& list) { + if (result != net::OK) { + VLOG(1) << "DNS resolving error:" << net::ErrorToString(result) + << " for host: " << prefix_ + resolving_host_; + } + if (complete_callback_for_testing_) + std::move(complete_callback_for_testing_).Run(); +} + +void IPFSHostResolver::OnTextResults(const std::vector& results) { + VLOG(2) << results.size() + << " TXT records resolved for host: " << prefix_ + resolving_host_; + std::string dnslink = GetDNSRecordValue(results, kDnsLinkHeader); + has_dnslink_ = !dnslink.empty(); + // We intentionally ignore the value since only its presence is important + // to us. https://docs.ipfs.io/concepts/dnslink/#publish-using-a-subdomain + if (!has_dnslink_) + return; + + if (resolved_callback_) + std::move(resolved_callback_).Run(resolving_host_); +} + +} // namespace ipfs diff --git a/browser/ipfs/ipfs_host_resolver.h b/browser/ipfs/ipfs_host_resolver.h new file mode 100644 index 000000000000..35e244c38d9e --- /dev/null +++ b/browser/ipfs/ipfs_host_resolver.h @@ -0,0 +1,65 @@ +/* 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_IPFS_IPFS_HOST_RESOLVER_H_ +#define BRAVE_BROWSER_IPFS_IPFS_HOST_RESOLVER_H_ + +#include +#include +#include + +#include "base/callback_forward.h" +#include "net/base/host_port_pair.h" +#include "net/base/network_isolation_key.h" +#include "net/dns/public/dns_query_type.h" +#include "services/network/public/cpp/resolve_host_client_base.h" +#include "services/network/public/mojom/host_resolver.mojom.h" +#include "services/network/public/mojom/network_context.mojom.h" + +namespace ipfs { + +// Resolves DNS TXT record for hosts. If prefix passed then +// automatically adds it to the host. +class IPFSHostResolver : public network::ResolveHostClientBase { + public: + explicit IPFSHostResolver(network::mojom::NetworkContext* network_context, + const std::string& prefix = std::string()); + ~IPFSHostResolver() override; + + using HostTextResultsCallback = + base::OnceCallback; + + virtual void Resolve(const net::HostPortPair& host, + const net::NetworkIsolationKey& isolation_key, + net::DnsQueryType dns_query_type, + HostTextResultsCallback callback); + + std::string host() const { return resolving_host_; } + + void SetCompleteCallbackForTesting(base::OnceClosure complete_callback) { + complete_callback_for_testing_ = std::move(complete_callback); + } + + private: + // network::mojom::ResolveHostClient implementation: + void OnComplete( + int result, + const net::ResolveErrorInfo& resolve_error_info, + const base::Optional& resolved_addresses) override; + void OnTextResults(const std::vector& text_results) override; + + std::string resolving_host_; + std::string prefix_; + bool has_dnslink_ = false; + network::mojom::NetworkContext* network_context_ = nullptr; + HostTextResultsCallback resolved_callback_; + base::OnceClosure complete_callback_for_testing_; + + mojo::Receiver receiver_{this}; +}; + +} // namespace ipfs + +#endif // BRAVE_BROWSER_IPFS_IPFS_HOST_RESOLVER_H_ diff --git a/browser/ipfs/ipfs_host_resolver_unittest.cc b/browser/ipfs/ipfs_host_resolver_unittest.cc new file mode 100644 index 000000000000..7dae98462b0c --- /dev/null +++ b/browser/ipfs/ipfs_host_resolver_unittest.cc @@ -0,0 +1,244 @@ +/* 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/ipfs/ipfs_host_resolver.h" + +#include +#include + +#include "base/run_loop.h" +#include "base/test/bind.h" +#include "base/test/task_environment.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/test/base/scoped_testing_local_state.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/browser/network_service_instance.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/test/browser_task_environment.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "services/network/host_resolver.h" +#include "services/network/network_context.h" +#include "services/network/network_service.h" +#include "services/network/public/cpp/resolve_host_client_base.h" +#include "services/network/public/mojom/host_resolver.mojom.h" +#include "services/network/public/mojom/network_context.mojom.h" +#include "services/network/test/test_network_context.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +class FakeHostResolver : public network::mojom::HostResolver { + public: + explicit FakeHostResolver(const std::string& expected_host) + : expected_host_(expected_host) {} + ~FakeHostResolver() override {} + + // network::mojom::HostResolver + void ResolveHost(const net::HostPortPair& host, + const net::NetworkIsolationKey& network_isolation_key, + network::mojom::ResolveHostParametersPtr parameters, + mojo::PendingRemote + pending_response_client) override { + EXPECT_EQ(expected_host_, host.host()); + EXPECT_EQ(parameters->dns_query_type, net::DnsQueryType::TXT); + mojo::Remote response_client; + response_client.Bind(std::move(pending_response_client)); + response_client->OnTextResults(text_results_); + resolve_host_called_++; + } + + void MdnsListen( + const ::net::HostPortPair& host, + ::net::DnsQueryType query_type, + ::mojo::PendingRemote response_client, + MdnsListenCallback callback) override {} + + int resolve_host_called() { return resolve_host_called_; } + + void RespondTextResults(const std::vector& text_results) { + text_results_ = text_results; + } + + void SetExpectedHost(const std::string& expected_host) { + expected_host_ = expected_host; + } + + protected: + int resolve_host_called_ = 0; + + private: + std::vector text_results_; + std::string expected_host_; +}; + +class FakeHostResolverFail : public FakeHostResolver { + public: + explicit FakeHostResolverFail(const std::string& expected_host) + : FakeHostResolver(expected_host) {} + ~FakeHostResolverFail() override {} + + // network::mojom::HostResolver + void ResolveHost(const net::HostPortPair& host, + const net::NetworkIsolationKey& network_isolation_key, + network::mojom::ResolveHostParametersPtr parameters, + mojo::PendingRemote + pending_response_client) override { + mojo::Remote response_client; + response_client.Bind(std::move(pending_response_client)); + response_client->OnComplete(-2, net::ResolveErrorInfo(), base::nullopt); + resolve_host_called_++; + } +}; + +class FakeNetworkContext : public network::TestNetworkContext { + public: + FakeNetworkContext() {} + ~FakeNetworkContext() override {} + + // network::mojom::HostResolver + void ResolveHost(const net::HostPortPair& host, + const net::NetworkIsolationKey& network_isolation_key, + network::mojom::ResolveHostParametersPtr parameters, + mojo::PendingRemote + pending_response_client) override { + DCHECK(host_resolver_); + host_resolver_->ResolveHost(host, network_isolation_key, + std::move(parameters), + std::move(pending_response_client)); + } + + void SetHostResolver( + std::unique_ptr host_resolver) { + host_resolver_ = std::move(host_resolver); + } + + private: + std::unique_ptr host_resolver_; +}; + +class IPFSHostResolverTest : public testing::Test { + public: + IPFSHostResolverTest() { + local_state_ = std::make_unique( + TestingBrowserProcess::GetGlobal()); + network_context_.reset(new FakeNetworkContext()); + } + + void HostResolvedCallback(base::OnceClosure callback, + const std::string& expected_host, + const std::string& host) { + EXPECT_EQ(expected_host, host); + resolved_callback_called_++; + if (callback) + std::move(callback).Run(); + } + + FakeNetworkContext* GetNetworkContext() { return network_context_.get(); } + + void SetResolvedCallbackCalled(int value) { + resolved_callback_called_ = value; + } + int resolved_callback_called() const { return resolved_callback_called_; } + + content::BrowserTaskEnvironment task_environment_; + int resolved_callback_called_ = 0; + std::unique_ptr network_context_; + std::unique_ptr local_state_; + std::unique_ptr stub_resolver_config_reader_; + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +TEST_F(IPFSHostResolverTest, PrefixRunSuccess) { + std::string prefix = "__dnslink."; + std::vector success = {"dnslink=abc", "a", "a=b", ""}; + std::string host = "example.com"; + + std::unique_ptr fake_host_resolver( + new FakeHostResolver(prefix + host)); + fake_host_resolver->RespondTextResults(success); + auto* network_context = GetNetworkContext(); + auto* fake_host_resolver_raw = fake_host_resolver.get(); + network_context->SetHostResolver(std::move(fake_host_resolver)); + base::RunLoop run_loop; + ipfs::IPFSHostResolver ipfs_resolver(network_context, prefix); + + SetResolvedCallbackCalled(0); + ipfs_resolver.Resolve( + net::HostPortPair(host, 11), net::NetworkIsolationKey(), + net::DnsQueryType::TXT, + base::BindOnce(&IPFSHostResolverTest::HostResolvedCallback, + weak_ptr_factory_.GetWeakPtr(), run_loop.QuitClosure(), + host)); + + run_loop.Run(); + EXPECT_EQ(ipfs_resolver.host(), host); + EXPECT_EQ(fake_host_resolver_raw->resolve_host_called(), 1); + EXPECT_EQ(resolved_callback_called(), 1); +} + +TEST_F(IPFSHostResolverTest, SuccessOnReuse) { + std::string prefix = "__dnslink."; + std::vector success = {"dnslink=abc", "a", "a=b", ""}; + std::string host = "example.com"; + + std::unique_ptr fake_host_resolver( + new FakeHostResolver(prefix + host)); + fake_host_resolver->RespondTextResults(success); + auto* network_context = GetNetworkContext(); + auto* fake_host_resolver_raw = fake_host_resolver.get(); + network_context->SetHostResolver(std::move(fake_host_resolver)); + base::RunLoop run_loop; + ipfs::IPFSHostResolver ipfs_resolver(network_context, prefix); + + SetResolvedCallbackCalled(0); + ipfs_resolver.Resolve( + net::HostPortPair(host, 11), net::NetworkIsolationKey(), + net::DnsQueryType::TXT, + base::BindOnce(&IPFSHostResolverTest::HostResolvedCallback, + weak_ptr_factory_.GetWeakPtr(), run_loop.QuitClosure(), + host)); + + run_loop.Run(); + EXPECT_EQ(ipfs_resolver.host(), host); + EXPECT_EQ(fake_host_resolver_raw->resolve_host_called(), 1); + EXPECT_EQ(resolved_callback_called(), 1); + + ipfs_resolver.Resolve( + net::HostPortPair(host, 11), net::NetworkIsolationKey(), + net::DnsQueryType::TXT, + base::BindOnce( + [](const std::string& expected_host, const std::string& host) { + EXPECT_EQ(expected_host, host); + }, + host)); + EXPECT_EQ(fake_host_resolver_raw->resolve_host_called(), 1); + EXPECT_EQ(resolved_callback_called(), 1); +} + +TEST_F(IPFSHostResolverTest, ResolutionFailed) { + std::string host = "example.com"; + std::unique_ptr fake_host_resolver( + new FakeHostResolverFail(host)); + auto* network_context = GetNetworkContext(); + auto* fake_host_resolver_raw = fake_host_resolver.get(); + network_context->SetHostResolver(std::move(fake_host_resolver)); + base::RunLoop run_loop; + ipfs::IPFSHostResolver ipfs_resolver(network_context); + ipfs_resolver.SetCompleteCallbackForTesting(run_loop.QuitClosure()); + SetResolvedCallbackCalled(0); + ipfs_resolver.Resolve( + net::HostPortPair(host, 11), net::NetworkIsolationKey(), + net::DnsQueryType::TXT, + base::BindOnce([](const std::string& host) { NOTREACHED(); })); + run_loop.Run(); + EXPECT_EQ(ipfs_resolver.host(), host); + EXPECT_EQ(fake_host_resolver_raw->resolve_host_called(), 1); + EXPECT_EQ(resolved_callback_called(), 0); +} diff --git a/browser/ipfs/ipfs_tab_helper.cc b/browser/ipfs/ipfs_tab_helper.cc index b73f0a52b490..876de1cfbed1 100644 --- a/browser/ipfs/ipfs_tab_helper.cc +++ b/browser/ipfs/ipfs_tab_helper.cc @@ -6,19 +6,40 @@ #include "brave/browser/ipfs/ipfs_tab_helper.h" #include +#include #include +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/containers/contains.h" +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "brave/browser/ipfs/ipfs_host_resolver.h" #include "brave/browser/ipfs/ipfs_service_factory.h" #include "brave/components/ipfs/ipfs_constants.h" #include "brave/components/ipfs/ipfs_utils.h" #include "brave/components/ipfs/pref_names.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/net/system_network_context_manager.h" #include "chrome/browser/shell_integration.h" +#include "chrome/common/channel_info.h" #include "components/prefs/pref_service.h" #include "components/user_prefs/user_prefs.h" #include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/navigation_handle.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/web_contents_delegate.h" +#include "net/http/http_status_code.h" namespace { + +// We have to check both domain and _dnslink.domain +// https://dnslink.io/#can-i-use-dnslink-in-non-dns-systems +const char kDnsDomainPrefix[] = "_dnslink."; + // Sets current executable as default protocol handler in a system. void SetupIPFSProtocolHandler(const std::string& protocol) { auto isDefaultCallback = [](const std::string& protocol, @@ -39,6 +60,7 @@ void SetupIPFSProtocolHandler(const std::string& protocol) { base::MakeRefCounted(protocol) ->StartCheckIsDefault(base::BindOnce(isDefaultCallback, protocol)); } + } // namespace namespace ipfs { @@ -48,6 +70,16 @@ IPFSTabHelper::~IPFSTabHelper() = default; IPFSTabHelper::IPFSTabHelper(content::WebContents* web_contents) : content::WebContentsObserver(web_contents) { pref_service_ = user_prefs::UserPrefs::Get(web_contents->GetBrowserContext()); + auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition( + web_contents->GetBrowserContext()); + + resolver_.reset(new IPFSHostResolver(storage_partition->GetNetworkContext(), + kDnsDomainPrefix)); + pref_change_registrar_.Init(pref_service_); + pref_change_registrar_.Add( + kIPFSResolveMethod, + base::BindRepeating(&IPFSTabHelper::UpdateDnsLinkButtonState, + base::Unretained(this))); } // static @@ -62,19 +94,104 @@ bool IPFSTabHelper::MaybeCreateForWebContents( return true; } -void IPFSTabHelper::DidFinishNavigation(content::NavigationHandle* handle) { - DCHECK(handle); - if (!handle->IsInMainFrame()) { +void IPFSTabHelper::DNSLinkHostResolved(const std::string& host) { + ipfs_resolved_host_ = host; + if (pref_service_->GetBoolean(kIPFSAutoRedirectDNSLink)) { + content::OpenURLParams params(GetIPFSResolvedURL(), content::Referrer(), + WindowOpenDisposition::CURRENT_TAB, + ui::PAGE_TRANSITION_LINK, false); + web_contents()->OpenURL(params); return; } + UpdateLocationBar(); +} + +void IPFSTabHelper::HostResolvedCallback(const std::string& host) { + GURL current = web_contents()->GetURL(); + if (current.host() != host || !current.SchemeIsHTTPOrHTTPS()) + return; + DNSLinkHostResolved(host); +} + +void IPFSTabHelper::UpdateLocationBar() { + if (web_contents()->GetDelegate()) + web_contents()->GetDelegate()->NavigationStateChanged( + web_contents(), content::INVALIDATE_TYPE_URL); +} + +GURL IPFSTabHelper::GetIPFSResolvedURL() const { + if (ipfs_resolved_host_.empty()) + return GURL(); + GURL current = web_contents()->GetURL(); + GURL::Replacements replacements; + replacements.SetSchemeStr(kIPNSScheme); + return current.ReplaceComponents(replacements); +} + +void IPFSTabHelper::ResolveIPFSLink() { + GURL current = web_contents()->GetURL(); + if (!current.SchemeIsHTTPOrHTTPS() || ipfs_resolved_host_ == current.host()) + return; + + const auto& host_port_pair = net::HostPortPair::FromURL(current); + auto resolved_callback = base::BindOnce(&IPFSTabHelper::HostResolvedCallback, + weak_ptr_factory_.GetWeakPtr()); + const auto& key = + web_contents()->GetMainFrame() + ? web_contents()->GetMainFrame()->GetNetworkIsolationKey() + : net::NetworkIsolationKey(); + resolver_->Resolve(host_port_pair, key, net::DnsQueryType::TXT, + std::move(resolved_callback)); +} + +bool IPFSTabHelper::IsDNSLinkCheckEnabled() const { + auto resolve_method = static_cast( + pref_service_->GetInteger(kIPFSResolveMethod)); + + return (resolve_method == ipfs::IPFSResolveMethodTypes::IPFS_LOCAL || + resolve_method == ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY); +} + +void IPFSTabHelper::UpdateDnsLinkButtonState() { + if (!IsDNSLinkCheckEnabled()) { + if (!ipfs_resolved_host_.empty()) { + ipfs_resolved_host_.erase(); + UpdateLocationBar(); + } + return; + } + + GURL current = web_contents()->GetURL(); + if (!ipfs_resolved_host_.empty() && resolver_->host() != current.host()) { + ipfs_resolved_host_.erase(); + UpdateLocationBar(); + } +} + +void IPFSTabHelper::MaybeShowDNSLinkButton(content::NavigationHandle* handle) { + UpdateDnsLinkButtonState(); + if (!IsDNSLinkCheckEnabled() || !handle->GetResponseHeaders()) + return; + GURL current = web_contents()->GetURL(); + if (!ipfs_resolved_host_.empty() || !current.SchemeIsHTTPOrHTTPS() || + IsDefaultGatewayURL(current, web_contents()->GetBrowserContext())) + return; + int response_code = handle->GetResponseHeaders()->response_code(); + if (response_code >= net::HttpStatusCode::HTTP_INTERNAL_SERVER_ERROR && + response_code <= net::HttpStatusCode::HTTP_VERSION_NOT_SUPPORTED) { + ResolveIPFSLink(); + } else if (handle->GetResponseHeaders()->HasHeader("x-ipfs-path")) { + DNSLinkHostResolved(current.host()); + } +} + +void IPFSTabHelper::MaybeSetupIpfsProtocolHandlers(const GURL& url) { auto resolve_method = static_cast( pref_service_->GetInteger(kIPFSResolveMethod)); auto* browser_context = web_contents()->GetBrowserContext(); if (resolve_method == ipfs::IPFSResolveMethodTypes::IPFS_ASK && - handle->GetResponseHeaders() && - handle->GetResponseHeaders()->HasHeader("x-ipfs-path") && - IsDefaultGatewayURL(handle->GetURL(), browser_context)) { + IsDefaultGatewayURL(url, browser_context)) { auto infobar_count = pref_service_->GetInteger(kIPFSInfobarCount); if (!infobar_count) { pref_service_->SetInteger(kIPFSInfobarCount, infobar_count + 1); @@ -84,6 +201,19 @@ void IPFSTabHelper::DidFinishNavigation(content::NavigationHandle* handle) { } } +void IPFSTabHelper::DidFinishNavigation(content::NavigationHandle* handle) { + DCHECK(handle); + if (!handle->IsInMainFrame() || !handle->HasCommitted() || + handle->IsSameDocument()) { + return; + } + if (handle->GetResponseHeaders() && + handle->GetResponseHeaders()->HasHeader("x-ipfs-path")) { + MaybeSetupIpfsProtocolHandlers(handle->GetURL()); + } + MaybeShowDNSLinkButton(handle); +} + WEB_CONTENTS_USER_DATA_KEY_IMPL(IPFSTabHelper) } // namespace ipfs diff --git a/browser/ipfs/ipfs_tab_helper.h b/browser/ipfs/ipfs_tab_helper.h index 066375f21497..9cf75b0033a4 100644 --- a/browser/ipfs/ipfs_tab_helper.h +++ b/browser/ipfs/ipfs_tab_helper.h @@ -7,7 +7,12 @@ #define BRAVE_BROWSER_IPFS_IPFS_TAB_HELPER_H_ #include +#include +#include +#include +#include "brave/browser/ipfs/ipfs_host_resolver.h" +#include "components/prefs/pref_change_registrar.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" @@ -20,6 +25,8 @@ class PrefService; namespace ipfs { +class IPFSHostResolver; + // Determines if IPFS should be active for a given top-level navigation. class IPFSTabHelper : public content::WebContentsObserver, public content::WebContentsUserData { @@ -30,17 +37,36 @@ class IPFSTabHelper : public content::WebContentsObserver, IPFSTabHelper& operator=(IPFSTabHelper&) = delete; static bool MaybeCreateForWebContents(content::WebContents* web_contents); + GURL GetIPFSResolvedURL() const; + + void SetResolverForTesting(std::unique_ptr resolver) { + resolver_ = std::move(resolver); + } private: friend class content::WebContentsUserData; explicit IPFSTabHelper(content::WebContents* web_contents); + bool IsDNSLinkCheckEnabled() const; + void DNSLinkHostResolved(const std::string& host); + void MaybeShowDNSLinkButton(content::NavigationHandle* handle); + void UpdateDnsLinkButtonState(); + + void MaybeSetupIpfsProtocolHandlers(const GURL& url); + // content::WebContentsObserver void DidFinishNavigation( content::NavigationHandle* navigation_handle) override; + void UpdateLocationBar(); - PrefService* pref_service_ = nullptr; + void ResolveIPFSLink(); + void HostResolvedCallback(const std::string& host); + PrefService* pref_service_ = nullptr; + PrefChangeRegistrar pref_change_registrar_; + std::string ipfs_resolved_host_; + std::unique_ptr resolver_; + base::WeakPtrFactory weak_ptr_factory_{this}; WEB_CONTENTS_USER_DATA_KEY_DECL(); }; diff --git a/browser/ipfs/ipfs_tab_helper_browsertest.cc b/browser/ipfs/ipfs_tab_helper_browsertest.cc new file mode 100644 index 000000000000..2d049d65cf5b --- /dev/null +++ b/browser/ipfs/ipfs_tab_helper_browsertest.cc @@ -0,0 +1,262 @@ +/* Copyright (c) 2019 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/ipfs/ipfs_tab_helper.h" + +#include "base/path_service.h" +#include "brave/browser/ipfs/ipfs_host_resolver.h" +#include "brave/browser/ipfs/ipfs_service_factory.h" +#include "brave/components/ipfs/ipfs_constants.h" +#include "brave/components/ipfs/pref_names.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/chrome_paths.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/user_prefs/user_prefs.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/controllable_http_response.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "net/test/embedded_test_server/http_request.h" +#include "net/test/embedded_test_server/http_response.h" + +using content::NavigationHandle; +using content::WebContents; +using content::WebContentsObserver; + +class IpfsTabHelperBrowserTest : public InProcessBrowserTest { + public: + IpfsTabHelperBrowserTest() + : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + host_resolver()->AddRule("*", "127.0.0.1"); + + embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data"); + https_server_.ServeFilesFromSourceDirectory("content/test/data"); + https_server_.RegisterRequestHandler( + base::BindRepeating(&IpfsTabHelperBrowserTest::ResponseHandler)); + ASSERT_TRUE(https_server_.Start()); + ASSERT_TRUE(embedded_test_server()->Start()); + } + + content::WebContents* active_contents() { + return browser()->tab_strip_model()->GetActiveWebContents(); + } + + GURL ReplaceScheme(const GURL& current, const std::string& new_scheme) { + GURL::Replacements replacements; + replacements.SetSchemeStr(new_scheme); + return current.ReplaceComponents(replacements); + } + + static std::unique_ptr ResponseHandler( + const net::test_server::HttpRequest& request) { + std::unique_ptr http_response( + new net::test_server::BasicHttpResponse); + + bool respond_error = (request.relative_url == "/5xx.html"); + auto code = respond_error ? net::HTTP_INTERNAL_SERVER_ERROR : net::HTTP_OK; + http_response->set_code(code); + if (!respond_error) + http_response->AddCustomHeader("x-ipfs-path", "test"); + return std::move(http_response); + } + net::EmbeddedTestServer https_server_; +}; + +class FakeIPFSHostResolver : public ipfs::IPFSHostResolver { + public: + explicit FakeIPFSHostResolver(network::mojom::NetworkContext* context) + : ipfs::IPFSHostResolver(context) {} + ~FakeIPFSHostResolver() override {} + void Resolve(const net::HostPortPair& host, + const net::NetworkIsolationKey& isolation_key, + net::DnsQueryType dns_query_type, + HostTextResultsCallback callback) override { + resolve_called_++; + if (callback) + std::move(callback).Run(host.host()); + } + + bool resolve_called() const { return resolve_called_ == 1; } + + private: + int resolve_called_ = 0; +}; + +IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolvedIPFSLinkLocal) { + ASSERT_TRUE( + ipfs::IPFSTabHelper::MaybeCreateForWebContents(active_contents())); + ipfs::IPFSTabHelper* helper = + ipfs::IPFSTabHelper::FromWebContents(active_contents()); + if (!helper) + return; + auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition( + active_contents()->GetBrowserContext()); + std::unique_ptr resolver( + new FakeIPFSHostResolver(storage_partition->GetNetworkContext())); + FakeIPFSHostResolver* resolver_raw = resolver.get(); + helper->SetResolverForTesting(std::move(resolver)); + auto* prefs = + user_prefs::UserPrefs::Get(active_contents()->GetBrowserContext()); + prefs->SetInteger(kIPFSResolveMethod, + static_cast(ipfs::IPFSResolveMethodTypes::IPFS_LOCAL)); + + const GURL test_url = https_server_.GetURL("/empty.html"); + ui_test_utils::NavigateToURL(browser(), test_url); + ASSERT_TRUE(WaitForLoadStop(active_contents())); + ASSERT_FALSE(resolver_raw->resolve_called()); + GURL ipns = ReplaceScheme(test_url, ipfs::kIPNSScheme); + EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), ipns.spec()); +} + +IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolvedIPFSLinkGateway) { + ASSERT_TRUE( + ipfs::IPFSTabHelper::MaybeCreateForWebContents(active_contents())); + ipfs::IPFSTabHelper* helper = + ipfs::IPFSTabHelper::FromWebContents(active_contents()); + if (!helper) + return; + auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition( + active_contents()->GetBrowserContext()); + std::unique_ptr resolver( + new FakeIPFSHostResolver(storage_partition->GetNetworkContext())); + FakeIPFSHostResolver* resolver_raw = resolver.get(); + helper->SetResolverForTesting(std::move(resolver)); + auto* prefs = + user_prefs::UserPrefs::Get(active_contents()->GetBrowserContext()); + prefs->SetInteger( + kIPFSResolveMethod, + static_cast(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY)); + + const GURL test_url = https_server_.GetURL("/empty.html"); + ui_test_utils::NavigateToURL(browser(), test_url); + ASSERT_TRUE(WaitForLoadStop(active_contents())); + ASSERT_FALSE(resolver_raw->resolve_called()); + GURL ipns = ReplaceScheme(test_url, ipfs::kIPNSScheme); + EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), ipns.spec()); +} + +IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, NoResolveIPFSLinkCalledMode) { + ASSERT_TRUE( + ipfs::IPFSTabHelper::MaybeCreateForWebContents(active_contents())); + ipfs::IPFSTabHelper* helper = + ipfs::IPFSTabHelper::FromWebContents(active_contents()); + if (!helper) + return; + auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition( + active_contents()->GetBrowserContext()); + std::unique_ptr resolver( + new FakeIPFSHostResolver(storage_partition->GetNetworkContext())); + FakeIPFSHostResolver* resolver_raw = resolver.get(); + helper->SetResolverForTesting(std::move(resolver)); + auto* prefs = + user_prefs::UserPrefs::Get(active_contents()->GetBrowserContext()); + prefs->SetInteger(kIPFSResolveMethod, + static_cast(ipfs::IPFSResolveMethodTypes::IPFS_ASK)); + + GURL test_url = https_server_.GetURL("/empty.html"); + ui_test_utils::NavigateToURL(browser(), test_url); + ASSERT_TRUE(WaitForLoadStop(active_contents())); + ASSERT_FALSE(resolver_raw->resolve_called()); + EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), std::string()); + + prefs->SetInteger( + kIPFSResolveMethod, + static_cast(ipfs::IPFSResolveMethodTypes::IPFS_DISABLED)); + + ui_test_utils::NavigateToURL(browser(), test_url); + ASSERT_TRUE(WaitForLoadStop(active_contents())); + ASSERT_FALSE(resolver_raw->resolve_called()); + EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), std::string()); +} + +IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, + NoResolveIPFSLinkCalledHeader) { + ASSERT_TRUE( + ipfs::IPFSTabHelper::MaybeCreateForWebContents(active_contents())); + ipfs::IPFSTabHelper* helper = + ipfs::IPFSTabHelper::FromWebContents(active_contents()); + if (!helper) + return; + auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition( + active_contents()->GetBrowserContext()); + std::unique_ptr resolver( + new FakeIPFSHostResolver(storage_partition->GetNetworkContext())); + FakeIPFSHostResolver* resolver_raw = resolver.get(); + helper->SetResolverForTesting(std::move(resolver)); + auto* prefs = + user_prefs::UserPrefs::Get(active_contents()->GetBrowserContext()); + prefs->SetInteger(kIPFSResolveMethod, + static_cast(ipfs::IPFSResolveMethodTypes::IPFS_LOCAL)); + + GURL test_url = embedded_test_server()->GetURL("/empty.html"); + ui_test_utils::NavigateToURL(browser(), test_url); + ASSERT_TRUE(WaitForLoadStop(active_contents())); + ASSERT_FALSE(resolver_raw->resolve_called()); + EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), std::string()); +} + +IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolveIPFSLinkCalled5xx) { + ASSERT_TRUE( + ipfs::IPFSTabHelper::MaybeCreateForWebContents(active_contents())); + ipfs::IPFSTabHelper* helper = + ipfs::IPFSTabHelper::FromWebContents(active_contents()); + if (!helper) + return; + auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition( + active_contents()->GetBrowserContext()); + std::unique_ptr resolver( + new FakeIPFSHostResolver(storage_partition->GetNetworkContext())); + FakeIPFSHostResolver* resolver_raw = resolver.get(); + helper->SetResolverForTesting(std::move(resolver)); + auto* prefs = + user_prefs::UserPrefs::Get(active_contents()->GetBrowserContext()); + prefs->SetInteger( + kIPFSResolveMethod, + static_cast(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY)); + EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), std::string()); + ASSERT_FALSE(resolver_raw->resolve_called()); + const GURL test_url = https_server_.GetURL("/5xx.html"); + ui_test_utils::NavigateToURL(browser(), test_url); + ASSERT_FALSE(WaitForLoadStop(active_contents())); + ASSERT_TRUE(resolver_raw->resolve_called()); + GURL ipns = ReplaceScheme(test_url, ipfs::kIPNSScheme); + EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), ipns.spec()); +} + +IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolveNotCalled5xx) { + ASSERT_TRUE( + ipfs::IPFSTabHelper::MaybeCreateForWebContents(active_contents())); + ipfs::IPFSTabHelper* helper = + ipfs::IPFSTabHelper::FromWebContents(active_contents()); + if (!helper) + return; + auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition( + active_contents()->GetBrowserContext()); + std::unique_ptr resolver( + new FakeIPFSHostResolver(storage_partition->GetNetworkContext())); + FakeIPFSHostResolver* resolver_raw = resolver.get(); + helper->SetResolverForTesting(std::move(resolver)); + auto* prefs = + user_prefs::UserPrefs::Get(active_contents()->GetBrowserContext()); + prefs->SetInteger(kIPFSResolveMethod, + static_cast(ipfs::IPFSResolveMethodTypes::IPFS_ASK)); + EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), std::string()); + ASSERT_FALSE(resolver_raw->resolve_called()); + const GURL test_url = https_server_.GetURL("/5xx.html"); + ui_test_utils::NavigateToURL(browser(), test_url); + ASSERT_FALSE(WaitForLoadStop(active_contents())); + ASSERT_FALSE(resolver_raw->resolve_called()); + EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), std::string()); +} diff --git a/browser/resources/settings/brave_ipfs_page/brave_ipfs_page.html b/browser/resources/settings/brave_ipfs_page/brave_ipfs_page.html index ce9d9e607b3a..fce4f8aadd79 100644 --- a/browser/resources/settings/brave_ipfs_page/brave_ipfs_page.html +++ b/browser/resources/settings/brave_ipfs_page/brave_ipfs_page.html @@ -56,6 +56,13 @@ label="$i18n{ipfsAutoRedirectGatewayLabel}" sub-label="$i18n{ipfsAutoRedirectGatewayDesc}"> + + - + diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index 06293fdce868..ed4cb6a63e8b 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -369,7 +369,10 @@ source_set("ui") { "webui/settings/brave_default_extensions_handler.h", ] - deps += [ "//third_party/widevine/cdm:buildflags" ] + deps += [ + "//brave/components/ipfs/buildflags", + "//third_party/widevine/cdm:buildflags", + ] if (enable_widevine) { deps += [ "//brave/browser/widevine" ] @@ -394,11 +397,19 @@ source_set("ui") { ] } + if (ipfs_enabled) { + sources += [ + "views/location_bar/ipfs_location_view.cc", + "views/location_bar/ipfs_location_view.h", + ] + } + deps += [ "//brave/browser/extensions", "//brave/browser/resources/extensions:resources", "//brave/components/brave_extension:generated_resources", "//brave/components/brave_extension:static_resources", + "//brave/components/resources:static_resources", "//chrome/browser/extensions", "//components/sessions", "//extensions/browser", diff --git a/browser/ui/views/location_bar/brave_location_bar_view.cc b/browser/ui/views/location_bar/brave_location_bar_view.cc index 5e3e5911a7b4..15bc6c3b9221 100644 --- a/browser/ui/views/location_bar/brave_location_bar_view.cc +++ b/browser/ui/views/location_bar/brave_location_bar_view.cc @@ -23,6 +23,9 @@ #if BUILDFLAG(ENABLE_TOR) #include "brave/browser/ui/views/location_bar/onion_location_view.h" #endif +#if BUILDFLAG(IPFS_ENABLED) +#include "brave/browser/ui/views/location_bar/ipfs_location_view.h" +#endif namespace { @@ -72,6 +75,11 @@ void BraveLocationBarView::Init() { onion_location_view_ = new OnionLocationView(browser_->profile()); AddChildView(onion_location_view_); #endif +#if BUILDFLAG(IPFS_ENABLED) + ipfs_location_view_ = new IPFSLocationView(browser_->profile()); + AddChildView(ipfs_location_view_); +#endif + // brave action buttons brave_actions_ = new BraveActionsContainer(browser_, profile()); brave_actions_->Init(); @@ -94,6 +102,11 @@ void BraveLocationBarView::Update(content::WebContents* contents) { if (onion_location_view_) onion_location_view_->Update(contents); #endif +#if BUILDFLAG(IPFS_ENABLED) + if (ipfs_location_view_) + ipfs_location_view_->Update(contents); +#endif + LocationBarView::Update(contents); } @@ -109,6 +122,11 @@ void BraveLocationBarView::OnChanged() { onion_location_view_->Update( browser_->tab_strip_model()->GetActiveWebContents()); #endif +#if BUILDFLAG(IPFS_ENABLED) + if (ipfs_location_view_) + ipfs_location_view_->Update( + browser_->tab_strip_model()->GetActiveWebContents()); +#endif // OnChanged calls Layout LocationBarView::OnChanged(); @@ -120,6 +138,11 @@ std::vector BraveLocationBarView::GetTrailingViews() { if (onion_location_view_) views.push_back(onion_location_view_); #endif +#if BUILDFLAG(IPFS_ENABLED) + if (ipfs_location_view_) + views.push_back(ipfs_location_view_); +#endif + if (brave_actions_) views.push_back(brave_actions_); @@ -141,6 +164,14 @@ gfx::Size BraveLocationBarView::CalculatePreferredSize() const { min_size.Enlarge(extra_width, 0); } #endif +#if BUILDFLAG(IPFS_ENABLED) + if (ipfs_location_view_ && ipfs_location_view_->GetVisible()) { + const int extra_width = GetLayoutConstant(LOCATION_BAR_ELEMENT_PADDING) + + ipfs_location_view_->GetMinimumSize().width(); + min_size.Enlarge(extra_width, 0); + } +#endif + return min_size; } diff --git a/browser/ui/views/location_bar/brave_location_bar_view.h b/browser/ui/views/location_bar/brave_location_bar_view.h index 42241a275aaf..4eb3e6bcf11d 100644 --- a/browser/ui/views/location_bar/brave_location_bar_view.h +++ b/browser/ui/views/location_bar/brave_location_bar_view.h @@ -8,6 +8,7 @@ #include +#include "brave/components/ipfs/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" #include "chrome/browser/ui/views/location_bar/location_bar_view.h" @@ -20,6 +21,10 @@ class SkPath; class OnionLocationView; #endif +#if BUILDFLAG(IPFS_ENABLED) +class IPFSLocationView; +#endif + // The purposes of this subclass are to: // - Add the BraveActionsContainer to the location bar class BraveLocationBarView : public LocationBarView { @@ -33,6 +38,9 @@ class BraveLocationBarView : public LocationBarView { OnionLocationView* GetOnionLocationView() { return onion_location_view_; } #endif +#if BUILDFLAG(IPFS_ENABLED) + IPFSLocationView* GetIPFSLocationView() { return ipfs_location_view_; } +#endif // LocationBarView: std::vector GetTrailingViews() override; @@ -53,6 +61,9 @@ class BraveLocationBarView : public LocationBarView { #if BUILDFLAG(ENABLE_TOR) OnionLocationView* onion_location_view_ = nullptr; #endif +#if BUILDFLAG(IPFS_ENABLED) + IPFSLocationView* ipfs_location_view_ = nullptr; +#endif DISALLOW_COPY_AND_ASSIGN(BraveLocationBarView); }; diff --git a/browser/ui/views/location_bar/ipfs_location_view.cc b/browser/ui/views/location_bar/ipfs_location_view.cc new file mode 100644 index 000000000000..da16f3ef1411 --- /dev/null +++ b/browser/ui/views/location_bar/ipfs_location_view.cc @@ -0,0 +1,154 @@ +/* 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/ui/views/location_bar/ipfs_location_view.h" + +#include +#include + +#include "base/strings/utf_string_conversions.h" +#include "brave/app/vector_icons/vector_icons.h" +#include "brave/browser/ipfs/ipfs_tab_helper.h" +#include "brave/grit/brave_generated_resources.h" +#include "chrome/browser/profiles/profile_window.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/layout_constants.h" +#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h" +#include "components/grit/brave_components_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/models/image_model.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/color_palette.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/paint_vector_icon.h" +#include "ui/gfx/text_constants.h" +#include "ui/views/animation/ink_drop_impl.h" +#include "ui/views/background.h" +#include "ui/views/border.h" +#include "ui/views/controls/button/label_button.h" +#include "ui/views/controls/highlight_path_generator.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/painter.h" +#include "ui/views/view.h" + +namespace { + +constexpr SkColor kOpenInIpfsBg = SkColorSetRGB(0x6a, 0x37, 0x85); +constexpr SkColor kIconColor = SkColorSetRGB(0xf0, 0xf2, 0xff); +constexpr SkColor kTextColor = SK_ColorWHITE; +constexpr int kIconSize = 12; + +// Sets the focus and ink drop highlight path to match the background +// along with it's corner radius. +class HighlightPathGenerator : public views::HighlightPathGenerator { + public: + HighlightPathGenerator() = default; + + // views::HighlightPathGenerator: + SkPath GetHighlightPath(const views::View* view) override { + const gfx::Rect highlight_bounds = view->GetLocalBounds(); + const SkRect rect = RectToSkRect(highlight_bounds); + const int corner_radius = view->height() / 2; + return SkPath().addRoundRect(rect, corner_radius, corner_radius); + } + + private: + DISALLOW_COPY_AND_ASSIGN(HighlightPathGenerator); +}; + +class IPFSLocationButtonView : public views::LabelButton { + public: + explicit IPFSLocationButtonView(Profile* profile) + : LabelButton(base::BindRepeating(&IPFSLocationButtonView::ButtonPressed, + base::Unretained(this)), + l10n_util::GetStringUTF16(IDS_LOCATION_BAR_OPEN_IN_IPFS)), + profile_(profile) { + // Render vector icon + const gfx::ImageSkia image = + gfx::CreateVectorIcon(kOpenInIpfsIcon, kIconSize, kIconColor); + SetImageModel(views::Button::STATE_NORMAL, + ui::ImageModel::FromImageSkia(image)); + // Set style specifics + SetEnabledTextColors(kTextColor); + SetHorizontalAlignment(gfx::ALIGN_RIGHT); + SetImageLabelSpacing(6); + SetInkDropMode(InkDropMode::ON); + SetBorder(views::CreateEmptyBorder( + GetLayoutInsets(LOCATION_BAR_ICON_INTERIOR_PADDING))); + SetHasInkDropActionOnClick(true); + SetInkDropVisibleOpacity(kToolbarInkDropVisibleOpacity); + UpdateBorder(); + // Ensure focus ring follows border + views::HighlightPathGenerator::Install( + this, std::make_unique()); + } + + ~IPFSLocationButtonView() override {} + + void SetIPFSLocation(GURL location) { ipfs_location_ = location; } + + private: + // views::View + void Layout() override { + views::LabelButton::Layout(); + UpdateBorder(); + } + + void UpdateBorder() { + SetBackground( + views::CreateRoundedRectBackground(kOpenInIpfsBg, height() / 2)); + } + + void ButtonPressed() { + Browser* browser = chrome::FindTabbedBrowser(profile_, true); + if (!browser) + return; + content::OpenURLParams open_ipfs(ipfs_location_, content::Referrer(), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui::PAGE_TRANSITION_TYPED, false); + browser->OpenURL(open_ipfs); + } + + GURL ipfs_location_; + Profile* profile_; + + IPFSLocationButtonView(const IPFSLocationButtonView&) = delete; + IPFSLocationButtonView& operator=(const IPFSLocationButtonView&) = delete; +}; + +} // namespace + +IPFSLocationView::IPFSLocationView(Profile* profile) { + SetBorder(views::CreateEmptyBorder(gfx::Insets(3, 3))); + SetVisible(false); + // automatic layout + auto vertical_container_layout = std::make_unique( + views::BoxLayout::Orientation::kHorizontal); + vertical_container_layout->set_main_axis_alignment( + views::BoxLayout::MainAxisAlignment::kCenter); + vertical_container_layout->set_cross_axis_alignment( + views::BoxLayout::CrossAxisAlignment::kCenter); + SetLayoutManager(std::move(vertical_container_layout)); + + button_ = new IPFSLocationButtonView(profile); + AddChildView(button_); +} + +IPFSLocationView::~IPFSLocationView() {} + +void IPFSLocationView::Update(content::WebContents* web_contents) { + if (!web_contents) + return; + ipfs::IPFSTabHelper* helper = + ipfs::IPFSTabHelper::FromWebContents(web_contents); + if (!helper) + return; + auto ipfs_resolved_url = helper->GetIPFSResolvedURL(); + SetVisible(ipfs_resolved_url.is_valid()); + reinterpret_cast(button_)->SetIPFSLocation( + ipfs_resolved_url); +} diff --git a/browser/ui/views/location_bar/ipfs_location_view.h b/browser/ui/views/location_bar/ipfs_location_view.h new file mode 100644 index 000000000000..808263337bbe --- /dev/null +++ b/browser/ui/views/location_bar/ipfs_location_view.h @@ -0,0 +1,35 @@ +/* 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_UI_VIEWS_LOCATION_BAR_IPFS_LOCATION_VIEW_H_ +#define BRAVE_BROWSER_UI_VIEWS_LOCATION_BAR_IPFS_LOCATION_VIEW_H_ + +#include "ui/gfx/geometry/size.h" +#include "ui/views/controls/button/label_button.h" +#include "ui/views/controls/button/label_button_border.h" + +class Profile; + +namespace content { +class WebContents; +} // namespace content + +class IPFSLocationView : public views::View { + public: + explicit IPFSLocationView(Profile* profile); + ~IPFSLocationView() override; + + void Update(content::WebContents* web_contents); + + views::LabelButton* GetButton() { return button_; } + + private: + views::LabelButton* button_ = nullptr; + + IPFSLocationView(const IPFSLocationView&) = delete; + IPFSLocationView& operator=(const IPFSLocationView&) = delete; +}; + +#endif // BRAVE_BROWSER_UI_VIEWS_LOCATION_BAR_IPFS_LOCATION_VIEW_H_ 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 80e8537dfdc8..4db198608864 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 @@ -56,6 +56,7 @@ const char kBraveReleaseTagPrefix[] = const char kGoogleLoginLearnMoreURL[] = "https://github.com/brave/brave-browser/wiki/" "Allow-Google-login---Third-Parties-and-Extensions"; +const char kDNSLinkLearnMoreURL[] = "https://docs.ipfs.io/concepts/dnslink/"; void BraveAddCommonStrings(content::WebUIDataSource* html_source, Profile* profile) { @@ -239,6 +240,10 @@ void BraveAddCommonStrings(content::WebUIDataSource* html_source, IDS_SETTINGS_IPFS_AUTO_REDIRECT_GATEWAY_LABEL}, {"ipfsAutoRedirectGatewayDesc", IDS_SETTINGS_IPFS_AUTO_REDIRECT_GATEWAY_DESC}, + {"ipfsAutoRedirectDNSLinkLabel", + IDS_SETTINGS_IPFS_AUTO_REDIRECT_DNSLINK_RESOURCES_LABEL}, + {"ipfsAutoRedirectDNSLinkDesc", + IDS_SETTINGS_IPFS_AUTO_REDIRECT_DNSLINK_RESOURCES_DESC}, {"ipfsCompanionEnabledDesc", IDS_SETTINGS_IPFS_COMPANION_ENABLED_DESC}, {"mediaRouterEnabledDesc", IDS_SETTINGS_MEDIA_ROUTER_ENABLED_DESC}, {"torEnabledLabel", IDS_SETTINGS_ENABLE_TOR_TITLE}, @@ -263,6 +268,9 @@ void BraveAddCommonStrings(content::WebUIDataSource* html_source, base::ASCIIToUTF16(kWebRTCLearnMoreURL)); html_source->AddString("googleLoginLearnMoreURL", base::ASCIIToUTF16(kGoogleLoginLearnMoreURL)); + html_source->AddString("ipfsDNSLinkLearnMoreURL", + base::UTF8ToUTF16(kDNSLinkLearnMoreURL)); + html_source->AddString( "getMoreExtensionsUrl", base::ASCIIToUTF16( diff --git a/chromium_src/net/dns/host_cache.cc b/chromium_src/net/dns/host_cache.cc new file mode 100644 index 000000000000..e20a218e0ff6 --- /dev/null +++ b/chromium_src/net/dns/host_cache.cc @@ -0,0 +1,15 @@ +/* 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/. */ + +#define BRAVE_COPY_WITH_DEFAULT_PORT \ + if (text_records()) { \ + std::vector copy_text_records; \ + for (const auto& record : text_records().value()) \ + copy_text_records.push_back(record); \ + copy.set_text_records(std::move(copy_text_records)); \ + } + +#include "../../../../net/dns/host_cache.cc" +#undef BRAVE_COPY_WITH_DEFAULT_PORT diff --git a/chromium_src/services/network/resolve_host_request.cc b/chromium_src/services/network/resolve_host_request.cc new file mode 100644 index 000000000000..a83fd7e32ca2 --- /dev/null +++ b/chromium_src/services/network/resolve_host_request.cc @@ -0,0 +1,13 @@ +/* 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/. */ + +#define BRAVE_START \ + if (internal_request_->GetTextResults()) { \ + response_client->OnTextResults( \ + internal_request_->GetTextResults().value()); \ + } + +#include "../../../../services/network/resolve_host_request.cc" +#undef BRAVE_START diff --git a/components/ipfs/ipfs_service.cc b/components/ipfs/ipfs_service.cc index 5ad00072a807..40ba16f829ba 100644 --- a/components/ipfs/ipfs_service.cc +++ b/components/ipfs/ipfs_service.cc @@ -115,6 +115,7 @@ void IpfsService::RegisterPrefs(PrefRegistrySimple* registry) { static_cast(ipfs::IPFSResolveMethodTypes::IPFS_ASK)); registry->RegisterBooleanPref(kIPFSAutoFallbackToGateway, false); registry->RegisterBooleanPref(kIPFSAutoRedirectGateway, false); + registry->RegisterBooleanPref(kIPFSAutoRedirectDNSLink, false); registry->RegisterIntegerPref(kIPFSInfobarCount, 0); registry->RegisterStringPref(kIPFSPublicGatewayAddress, kDefaultIPFSGateway); registry->RegisterFilePathPref(kIPFSBinaryPath, base::FilePath()); diff --git a/components/ipfs/pref_names.cc b/components/ipfs/pref_names.cc index 7ee489dfe112..a8ea250c9831 100644 --- a/components/ipfs/pref_names.cc +++ b/components/ipfs/pref_names.cc @@ -24,6 +24,9 @@ const char kIPFSAutoFallbackToGateway[] = "brave.ipfs.auto_fallback_to_gateway"; // header to the configured Brave IPFS gateway. const char kIPFSAutoRedirectGateway[] = "brave.ipfs.auto_redirect_gateway"; +// Used to automatically redirect for DNSLink resources +const char kIPFSAutoRedirectDNSLink[] = "brave.ipfs.auto_redirect_dnslink"; + // The number of times the infobar is shown to ask the user to install IPFS const char kIPFSInfobarCount[] = "brave.ipfs.infobar_count"; diff --git a/components/ipfs/pref_names.h b/components/ipfs/pref_names.h index 317279d8a7ff..6a85fd6dabe6 100644 --- a/components/ipfs/pref_names.h +++ b/components/ipfs/pref_names.h @@ -10,6 +10,7 @@ extern const char kIPFSResolveMethod[]; extern const char kIPFSBinaryPath[]; extern const char kIPFSAutoFallbackToGateway[]; extern const char kIPFSAutoRedirectGateway[]; +extern const char kIPFSAutoRedirectDNSLink[]; extern const char kIPFSInfobarCount[]; extern const char kIPFSEnabled[]; extern const char kIPFSPublicGatewayAddress[]; diff --git a/components/resources/ipfs_strings.grdp b/components/resources/ipfs_strings.grdp index bb6beb3871e3..b624a5ca9c13 100644 --- a/components/resources/ipfs_strings.grdp +++ b/components/resources/ipfs_strings.grdp @@ -97,7 +97,7 @@ Installing... - Waiting for peers... + Looking for peers... Try again diff --git a/patches/net-dns-host_cache.cc.patch b/patches/net-dns-host_cache.cc.patch new file mode 100644 index 000000000000..ac0689a6e627 --- /dev/null +++ b/patches/net-dns-host_cache.cc.patch @@ -0,0 +1,12 @@ +diff --git a/net/dns/host_cache.cc b/net/dns/host_cache.cc +index b5677269f0658878a1226d6464ef39798f4be68e..3dfab2a9c6ac833cf639d3c01d24e2f633cc4790 100644 +--- a/net/dns/host_cache.cc ++++ b/net/dns/host_cache.cc +@@ -220,6 +220,7 @@ HostCache::Entry HostCache::Entry::CopyWithDefaultPort(uint16_t port) const { + } + copy.set_hostnames(std::move(hostnames_with_port)); + } ++ BRAVE_COPY_WITH_DEFAULT_PORT + + return copy; + } diff --git a/patches/services-network-resolve_host_request.cc.patch b/patches/services-network-resolve_host_request.cc.patch new file mode 100644 index 000000000000..64d245897f87 --- /dev/null +++ b/patches/services-network-resolve_host_request.cc.patch @@ -0,0 +1,12 @@ +diff --git a/services/network/resolve_host_request.cc b/services/network/resolve_host_request.cc +index 937cfa8d35461b1cc8a0edf623c86ef8dcbebe3b..da7b8a86125ddeaff7f959bf87281972dbf94666 100644 +--- a/services/network/resolve_host_request.cc ++++ b/services/network/resolve_host_request.cc +@@ -59,6 +59,7 @@ int ResolveHostRequest::Start( + mojo::Remote response_client( + std::move(pending_response_client)); + if (rv != net::ERR_IO_PENDING) { ++ BRAVE_START + response_client->OnComplete(rv, GetResolveErrorInfo(), GetAddressResults()); + return rv; + } diff --git a/test/BUILD.gn b/test/BUILD.gn index c7111c909408..bde5351b1f7d 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -451,6 +451,7 @@ test("brave_unit_tests") { if (ipfs_enabled) { sources += [ "//brave/browser/ipfs/content_browser_client_helper_unittest.cc", + "//brave/browser/ipfs/ipfs_host_resolver_unittest.cc", "//brave/browser/net/ipfs_redirect_network_delegate_helper_unittest.cc", ] deps += [ "//brave/components/ipfs" ] @@ -748,6 +749,7 @@ if (!is_android) { sources += [ "//brave/browser/extensions/api/ipfs_apitest.cc", "//brave/browser/ipfs/ipfs_policy_browsertest.cc", + "//brave/browser/ipfs/ipfs_tab_helper_browsertest.cc", "//brave/browser/net/ipfs_redirect_network_delegate_helper_browsertest.cc", ] deps += [ "//brave/components/ipfs" ]