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" ]