From 19bb25f5fe21be5897def6e1b07971bff0890f8c Mon Sep 17 00:00:00 2001 From: oisupov Date: Tue, 13 Sep 2022 22:36:21 +0700 Subject: [PATCH] Fix x-ipfs-path handling Resolves https://github.com/brave/brave-browser/issues/25281 Autoredirect to dnslink only if url has _dnslink DNS record. _dnslink has priority over x-ipfs-path content. When there is only x-ipfs-path header then translate url to https://gateway/ instead of ipns://original_url --- browser/ipfs/ipfs_host_resolver.cc | 3 + browser/ipfs/ipfs_host_resolver.h | 2 +- browser/ipfs/ipfs_host_resolver_unittest.cc | 11 +- browser/ipfs/ipfs_tab_helper.cc | 90 ++++--- browser/ipfs/ipfs_tab_helper.h | 42 ++- browser/ipfs/ipfs_tab_helper_browsertest.cc | 74 +++-- browser/ipfs/ipfs_tab_helper_unittest.cc | 283 ++++++++++++++------ 7 files changed, 346 insertions(+), 159 deletions(-) diff --git a/browser/ipfs/ipfs_host_resolver.cc b/browser/ipfs/ipfs_host_resolver.cc index 548a450b1c3b..f26d14eadb0f 100644 --- a/browser/ipfs/ipfs_host_resolver.cc +++ b/browser/ipfs/ipfs_host_resolver.cc @@ -85,6 +85,9 @@ void IPFSHostResolver::OnComplete( if (result != net::OK) { VLOG(1) << "DNS resolving error:" << net::ErrorToString(result) << " for host: " << prefix_ + resolving_host_; + if (resolved_callback_) { + std::move(resolved_callback_).Run(resolving_host_, absl::nullopt); + } } if (complete_callback_for_testing_) std::move(complete_callback_for_testing_).Run(); diff --git a/browser/ipfs/ipfs_host_resolver.h b/browser/ipfs/ipfs_host_resolver.h index 37aeb368d531..770304f04b28 100644 --- a/browser/ipfs/ipfs_host_resolver.h +++ b/browser/ipfs/ipfs_host_resolver.h @@ -31,7 +31,7 @@ class IPFSHostResolver : public network::ResolveHostClientBase { using HostTextResultsCallback = base::OnceCallback; + const absl::optional& dnslink)>; virtual void Resolve(const net::HostPortPair& host, const net::NetworkIsolationKey& isolation_key, diff --git a/browser/ipfs/ipfs_host_resolver_unittest.cc b/browser/ipfs/ipfs_host_resolver_unittest.cc index 067860b1c32d..4563d4b5efbd 100644 --- a/browser/ipfs/ipfs_host_resolver_unittest.cc +++ b/browser/ipfs/ipfs_host_resolver_unittest.cc @@ -134,7 +134,7 @@ class IPFSHostResolverTest : public testing::Test { void HostResolvedCallback(base::OnceClosure callback, const std::string& expected_host, const std::string& host, - const std::string& dnslink) { + const absl::optional& dnslink) { EXPECT_EQ(expected_host, host); resolved_callback_called_++; if (callback) @@ -218,7 +218,9 @@ TEST_F(IPFSHostResolverTest, SuccessOnReuse) { net::DnsQueryType::TXT, base::BindOnce( [](const std::string& expected_host, const std::string& host, - const std::string& dnslink) { EXPECT_EQ(expected_host, host); }, + const absl::optional& dnslink) { + EXPECT_EQ(expected_host, host); + }, host)); EXPECT_EQ(fake_host_resolver_raw->resolve_host_called(), 1); EXPECT_EQ(resolved_callback_called(), 1); @@ -239,8 +241,9 @@ TEST_F(IPFSHostResolverTest, ResolutionFailed) { ipfs_resolver.Resolve( net::HostPortPair(host, 11), net::NetworkIsolationKey(), net::DnsQueryType::TXT, - base::BindOnce([](const std::string& host, const std::string& dnslink) { - NOTREACHED(); + base::BindOnce([](const std::string& host, + const absl::optional& dnslink) { + EXPECT_FALSE(dnslink); })); run_loop.Run(); EXPECT_EQ(ipfs_resolver.host(), host); diff --git a/browser/ipfs/ipfs_tab_helper.cc b/browser/ipfs/ipfs_tab_helper.cc index cf47db1bd0d7..43a4dfb49161 100644 --- a/browser/ipfs/ipfs_tab_helper.cc +++ b/browser/ipfs/ipfs_tab_helper.cc @@ -94,9 +94,16 @@ bool IPFSTabHelper::MaybeCreateForWebContents( return true; } -void IPFSTabHelper::IPFSLinkResolved(const GURL& ipfs) { +void IPFSTabHelper::XIPFSPathLinkResolved(const GURL& ipfs) { ipfs_resolved_url_ = ipfs; - if (pref_service_->GetBoolean(kIPFSAutoRedirectDNSLink)) { + UpdateLocationBar(); +} + +void IPFSTabHelper::DNSLinkResolved(const GURL& ipfs) { + ipfs_resolved_url_ = ipfs; + DCHECK(ipfs.is_empty() || ipfs.SchemeIs(kIPNSScheme)); + if (pref_service_->GetBoolean(kIPFSAutoRedirectDNSLink) && + !ipfs_resolved_url_.is_empty()) { content::OpenURLParams params(GetIPFSResolvedURL(), content::Referrer(), WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_LINK, false); @@ -107,18 +114,23 @@ void IPFSTabHelper::IPFSLinkResolved(const GURL& ipfs) { UpdateLocationBar(); } -void IPFSTabHelper::HostResolvedCallback(const std::string& host, - const std::string& dnslink) { +void IPFSTabHelper::HostResolvedCallback( + absl::optional x_ipfs_path_header, + const std::string& host, + const absl::optional& dnslink) { GURL current = web_contents()->GetURL(); + if (current.host() != host || !current.SchemeIsHTTPOrHTTPS()) return; - if (dnslink.empty()) + if (!dnslink || dnslink.value().empty()) { + if (x_ipfs_path_header) { + XIPFSPathLinkResolved(ResolveXIPFSPathUrl(x_ipfs_path_header.value())); + } return; - GURL::Replacements replacements; - replacements.SetSchemeStr(kIPNSScheme); - GURL resolved_url(current.ReplaceComponents(replacements)); + } + GURL resolved_url = ResolveDNSLinkUrl(current); if (resolved_url.is_valid()) - IPFSLinkResolved(resolved_url); + DNSLinkResolved(resolved_url); } void IPFSTabHelper::UpdateLocationBar() { @@ -143,7 +155,8 @@ GURL IPFSTabHelper::GetIPFSResolvedURL() const { return ipfs_resolved_url_.ReplaceComponents(replacements); } -void IPFSTabHelper::ResolveIPFSLink() { +void IPFSTabHelper::CheckDNSLinkRecord( + absl::optional x_ipfs_path_header) { GURL current = web_contents()->GetURL(); if (!current.SchemeIsHTTPOrHTTPS()) return; @@ -151,7 +164,8 @@ void IPFSTabHelper::ResolveIPFSLink() { const auto& host_port_pair = net::HostPortPair::FromURL(current); auto resolved_callback = base::BindOnce(&IPFSTabHelper::HostResolvedCallback, - weak_ptr_factory_.GetWeakPtr()); + weak_ptr_factory_.GetWeakPtr(), + std::move(x_ipfs_path_header)); const auto& key = web_contents()->GetPrimaryMainFrame() ? web_contents()->GetPrimaryMainFrame()->GetNetworkIsolationKey() @@ -196,48 +210,42 @@ bool IPFSTabHelper::CanResolveURL(const GURL& url) const { return resolve; } -std::string IPFSTabHelper::GetPathForDNSLink(GURL url) { - if (ipfs::IsIPFSScheme(url)) { - std::string path = url.path(); - if (base::StartsWith(path, "//")) - return path.substr(1, path.size()); - return path; - } - return "/ipns/" + url.host() + url.path(); -} -// For DNSLink we are making urls like -// /ipns// -GURL IPFSTabHelper::ResolveDNSLinkURL(GURL url) { - if (!url.is_valid()) - return url; +// For x-ipfs-path header we are making urls like +// / +GURL IPFSTabHelper::ResolveXIPFSPathUrl( + const std::string& x_ipfs_path_header_value) { GURL gateway = ipfs::GetConfiguredBaseGateway(pref_service_, chrome::GetChannel()); GURL::Replacements replacements; - auto path = GetPathForDNSLink(url); - replacements.SetPathStr(path); + replacements.SetPathStr(x_ipfs_path_header_value); return gateway.ReplaceComponents(replacements); } -void IPFSTabHelper::MaybeShowDNSLinkButton( +// For _dnslink we just translate url to ipns:// scheme +GURL IPFSTabHelper::ResolveDNSLinkUrl(const GURL& url) { + GURL::Replacements replacements; + replacements.SetSchemeStr(kIPNSScheme); + return url.ReplaceComponents(replacements); +} + +void IPFSTabHelper::MaybeCheckDNSLinkRecord( const net::HttpResponseHeaders* headers) { UpdateDnsLinkButtonState(); auto current_url = GetCurrentPageURL(); + if (!IsDNSLinkCheckEnabled() || !headers || ipfs_resolved_url_.is_valid() || - !CanResolveURL(current_url)) + !CanResolveURL(current_url)) { return; + } int response_code = headers->response_code(); - if (response_code >= net::HttpStatusCode::HTTP_INTERNAL_SERVER_ERROR && - response_code <= net::HttpStatusCode::HTTP_VERSION_NOT_SUPPORTED) { - ResolveIPFSLink(); - } else if (headers->HasHeader(kIfpsPathHeader)) { - std::string ipfs_path_value; - if (!headers->GetNormalizedHeader(kIfpsPathHeader, &ipfs_path_value) || - ipfs_path_value.empty()) - return; - auto resolved_url = ResolveDNSLinkURL(current_url); - if (resolved_url.is_valid()) - IPFSLinkResolved(resolved_url); + std::string normalized_header; + if ((response_code >= net::HttpStatusCode::HTTP_INTERNAL_SERVER_ERROR && + response_code <= net::HttpStatusCode::HTTP_VERSION_NOT_SUPPORTED)) { + CheckDNSLinkRecord(absl::nullopt); + } else if (headers->GetNormalizedHeader(kIfpsPathHeader, + &normalized_header)) { + CheckDNSLinkRecord(normalized_header); } } @@ -265,7 +273,7 @@ void IPFSTabHelper::DidFinishNavigation(content::NavigationHandle* handle) { handle->GetResponseHeaders()->HasHeader(kIfpsPathHeader)) { MaybeSetupIpfsProtocolHandlers(handle->GetURL()); } - MaybeShowDNSLinkButton(handle->GetResponseHeaders()); + MaybeCheckDNSLinkRecord(handle->GetResponseHeaders()); } WEB_CONTENTS_USER_DATA_KEY_IMPL(IPFSTabHelper); diff --git a/browser/ipfs/ipfs_tab_helper.h b/browser/ipfs/ipfs_tab_helper.h index 581041769857..7cf659d6e1b4 100644 --- a/browser/ipfs/ipfs_tab_helper.h +++ b/browser/ipfs/ipfs_tab_helper.h @@ -54,9 +54,30 @@ class IPFSTabHelper : public content::WebContentsObserver, private: FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, CanResolveURLTest); - FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, URLResolvingTest); + FRIEND_TEST_ALL_PREFIXES( + IpfsTabHelperUnitTest, + TranslateUrlToIpns_When_HasDNSLinkRecord_AndXIPFSPathHeader); + FRIEND_TEST_ALL_PREFIXES( + IpfsTabHelperUnitTest, + TranslateUrlToIpns_When_HasDNSLinkRecord_AndOriginalPageFails_500); + FRIEND_TEST_ALL_PREFIXES( + IpfsTabHelperUnitTest, + TranslateUrlToIpns_When_HasDNSLinkRecord_AndOriginalPageFails_400); + FRIEND_TEST_ALL_PREFIXES( + IpfsTabHelperUnitTest, + TranslateUrlToIpns_When_HasDNSLinkRecord_AndOriginalPageFails_505); + FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, + DoNotTranslateUrlToIpns_When_NoDNSLinkRecord); + FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, + DoNotTranslateUrlToIpns_When_NoHeader_And_NoError); + FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, + DNSLinkRecordResolved_AutoRedirectDNSLink); + FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, + XIpfsPathHeaderUsed_IfNoDnsLinkRecord_IPFS); + FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, + XIpfsPathHeaderUsed_IfNoDnsLinkRecord_IPNS); + FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, ResolveXIPFSPathUrl); FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, GatewayResolving); - FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, ResolveDNSLinkURL); friend class content::WebContentsUserData; explicit IPFSTabHelper(content::WebContents* web_contents); @@ -64,10 +85,13 @@ class IPFSTabHelper : public content::WebContentsObserver, GURL GetCurrentPageURL() const; bool CanResolveURL(const GURL& url) const; bool IsDNSLinkCheckEnabled() const; - void IPFSLinkResolved(const GURL& ipfs); - void MaybeShowDNSLinkButton(const net::HttpResponseHeaders* headers); + void XIPFSPathLinkResolved(const GURL& ipfs); + void DNSLinkResolved(const GURL& ipfs); + void MaybeCheckDNSLinkRecord(const net::HttpResponseHeaders* headers); void UpdateDnsLinkButtonState(); - GURL ResolveDNSLinkURL(GURL url); + + GURL ResolveDNSLinkUrl(const GURL& url); + GURL ResolveXIPFSPathUrl(const std::string& x_ipfs_path_header_value); void MaybeSetupIpfsProtocolHandlers(const GURL& url); @@ -76,10 +100,10 @@ class IPFSTabHelper : public content::WebContentsObserver, content::NavigationHandle* navigation_handle) override; void UpdateLocationBar(); - void ResolveIPFSLink(); - std::string GetPathForDNSLink(GURL url); - void HostResolvedCallback(const std::string& host, - const std::string& dnslink); + void CheckDNSLinkRecord(absl::optional x_ipfs_path_header); + void HostResolvedCallback(absl::optional x_ipfs_path_header, + const std::string& host, + const absl::optional& dnslink); PrefService* pref_service_ = nullptr; PrefChangeRegistrar pref_change_registrar_; diff --git a/browser/ipfs/ipfs_tab_helper_browsertest.cc b/browser/ipfs/ipfs_tab_helper_browsertest.cc index 54564a60b65e..774a47134cd6 100644 --- a/browser/ipfs/ipfs_tab_helper_browsertest.cc +++ b/browser/ipfs/ipfs_tab_helper_browsertest.cc @@ -64,7 +64,7 @@ class IpfsTabHelperBrowserTest : public InProcessBrowserTest { new net::test_server::BasicHttpResponse); http_response->set_code(code_); - if (code_ == net::HTTP_OK) + if (code_ == net::HTTP_OK && !x_ipfs_path_.empty()) http_response->AddCustomHeader("x-ipfs-path", x_ipfs_path_); return std::move(http_response); } @@ -94,7 +94,7 @@ class FakeIPFSHostResolver : public ipfs::IPFSHostResolver { bool resolve_called() const { return resolve_called_ == 1; } - void SetDNSLinkToResopnd(const std::string& dnslink) { dnslink_ = dnslink; } + void SetDNSLinkToRespond(const std::string& dnslink) { dnslink_ = dnslink; } private: int resolve_called_ = 0; @@ -120,14 +120,15 @@ IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolvedIPFSLinkLocal) { static_cast(ipfs::IPFSResolveMethodTypes::IPFS_LOCAL)); GURL gateway = ipfs::GetConfiguredBaseGateway(prefs, chrome::GetChannel()); + // X-IPFS-Path header and no DNSLink SetXIpfsPathHeader("/ipfs/bafybeiemx/empty.html"); GURL test_url = https_server_.GetURL("/empty.html?query#ref"); ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); ASSERT_TRUE(WaitForLoadStop(active_contents())); - ASSERT_FALSE(resolver_raw->resolve_called()); + ASSERT_TRUE(resolver_raw->resolve_called()); auto resolved_url = helper->GetIPFSResolvedURL(); EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/ipns/" + test_url.host() + "/empty.html"); + EXPECT_EQ(resolved_url.path(), "/ipfs/bafybeiemx/empty.html"); EXPECT_EQ(resolved_url.query(), "query"); EXPECT_EQ(resolved_url.ref(), "ref"); @@ -137,7 +138,7 @@ IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolvedIPFSLinkLocal) { ASSERT_FALSE(resolver_raw->resolve_called()); resolved_url = helper->GetIPFSResolvedURL(); EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/ipns/" + test_url.host() + "/another.html"); + EXPECT_EQ(resolved_url.path(), "/ipfs/bafybeiemx/empty.html"); EXPECT_EQ(resolved_url.query(), "query"); EXPECT_EQ(resolved_url.ref(), "ref"); @@ -148,7 +149,7 @@ IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolvedIPFSLinkLocal) { ASSERT_FALSE(resolver_raw->resolve_called()); resolved_url = helper->GetIPFSResolvedURL(); EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/ipns/" + test_url.host() + "/"); + EXPECT_EQ(resolved_url.path(), "/ipns/brave.eth/empty.html"); EXPECT_EQ(resolved_url.query(), "query"); EXPECT_EQ(resolved_url.ref(), "ref"); @@ -160,8 +161,7 @@ IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolvedIPFSLinkLocal) { ASSERT_FALSE(resolver_raw->resolve_called()); resolved_url = helper->GetIPFSResolvedURL(); EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), - "/ipns/" + test_url.host() + "/ipfs/bafy/wiki/empty.html"); + EXPECT_EQ(resolved_url.path(), "/ipfs/bafy"); EXPECT_EQ(resolved_url.query(), "query"); EXPECT_EQ(resolved_url.ref(), "ref"); @@ -173,8 +173,7 @@ IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolvedIPFSLinkLocal) { ASSERT_FALSE(resolver_raw->resolve_called()); resolved_url = helper->GetIPFSResolvedURL(); EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), - "/ipns/" + test_url.host() + "/ipns/bafyb/wiki/empty.html"); + EXPECT_EQ(resolved_url.path(), "/ipns/bafyb"); EXPECT_EQ(resolved_url.query(), "query"); EXPECT_EQ(resolved_url.ref(), "ref"); } @@ -201,9 +200,9 @@ IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolvedIPFSLinkGateway) { const GURL test_url = https_server_.GetURL("/empty.html"); ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); ASSERT_TRUE(WaitForLoadStop(active_contents())); - ASSERT_FALSE(resolver_raw->resolve_called()); + ASSERT_TRUE(resolver_raw->resolve_called()); EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), - "https://dweb.link/ipns/" + test_url.host() + "/empty.html"); + "https://dweb.link/ipfs/bafybeiemx/empty.html"); } IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, NoResolveIPFSLinkCalledMode) { @@ -278,7 +277,7 @@ IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolveIPFSLinkCalled5xx) { std::unique_ptr resolver( new FakeIPFSHostResolver(storage_partition->GetNetworkContext())); FakeIPFSHostResolver* resolver_raw = resolver.get(); - resolver_raw->SetDNSLinkToResopnd("/ipfs/QmXoypiz"); + resolver_raw->SetDNSLinkToRespond("/ipfs/QmXoypiz"); helper->SetResolverForTesting(std::move(resolver)); auto* prefs = user_prefs::UserPrefs::Get(active_contents()->GetBrowserContext()); @@ -334,13 +333,18 @@ IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolvedIPFSLinkBad) { active_contents()->GetBrowserContext()->GetDefaultStoragePartition(); 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)); + SetXIpfsPathHeader(""); + resolver_raw->SetDNSLinkToRespond(""); + const GURL test_url = https_server_.GetURL("/empty.html"); ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); ASSERT_TRUE(WaitForLoadStop(active_contents())); @@ -380,35 +384,49 @@ IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, kIPFSResolveMethod, static_cast(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY)); - GURL test_url = embedded_test_server()->GetURL("/empty.html?query#ref"); + // Navigation with x-ipfs-path header and valid _dnslink record redirects to + // ipns:// url + GURL test_url = embedded_test_server()->GetURL("navigate_to.com", + "/empty.html?query#ref"); GURL gateway_url = embedded_test_server()->GetURL("a.com", "/"); + resolver_raw->SetDNSLinkToRespond("/ipns/a.com/"); + prefs->SetString(kIPFSPublicGatewayAddress, gateway_url.spec()); ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); ASSERT_TRUE(WaitForLoadStop(active_contents())); - ASSERT_FALSE(resolver_raw->resolve_called()); + ASSERT_TRUE(resolver_raw->resolve_called()); - GURL expected_first("http://a.com/ipns/" + test_url.host() + - "/empty.html?query#ref"); - GURL::Replacements first_replacements; - first_replacements.SetPortStr(gateway_url.port_piece()); - EXPECT_EQ(active_contents()->GetVisibleURL().spec(), - expected_first.ReplaceComponents(first_replacements)); + GURL::Replacements scheme_replacements; + scheme_replacements.SetSchemeStr(ipfs::kIPNSScheme); + + // Url will be translated to ipns:// scheme which will be translated to + // gateway url. + GURL expected_final_url; + ipfs::TranslateIPFSURI(test_url.ReplaceComponents(scheme_replacements), + &expected_final_url, gateway_url, false); + EXPECT_EQ(active_contents()->GetVisibleURL().spec(), expected_final_url); + + // Second one navigation also succeed GURL another_test_url = embedded_test_server()->GetURL("/another.html?query#ref"); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), another_test_url)); ASSERT_TRUE(WaitForLoadStop(active_contents())); ASSERT_FALSE(resolver_raw->resolve_called()); - GURL expected_second("http://a.com/ipns/" + test_url.host() + - "/another.html?query#ref"); - GURL::Replacements second_replacements; - second_replacements.SetPortStr(gateway_url.port_piece()); + // Url will be translated to ipns:// scheme which will be translated to + // gateway url. + GURL expected_second_final_url; + ipfs::TranslateIPFSURI( + another_test_url.ReplaceComponents(scheme_replacements), + &expected_second_final_url, gateway_url, false); + EXPECT_EQ(active_contents()->GetVisibleURL().spec(), - expected_second.ReplaceComponents(second_replacements)); + expected_second_final_url); + // Backward navigation also succeed active_contents()->GetController().GoBack(); ASSERT_TRUE(WaitForLoadStop(active_contents())); - EXPECT_EQ(active_contents()->GetVisibleURL(), - expected_first.ReplaceComponents(first_replacements)); + EXPECT_EQ(active_contents()->GetVisibleURL(), expected_final_url); } diff --git a/browser/ipfs/ipfs_tab_helper_unittest.cc b/browser/ipfs/ipfs_tab_helper_unittest.cc index f885a0095094..3c1ea225be09 100644 --- a/browser/ipfs/ipfs_tab_helper_unittest.cc +++ b/browser/ipfs/ipfs_tab_helper_unittest.cc @@ -17,12 +17,38 @@ #include "content/public/test/browser_task_environment.h" #include "content/public/test/test_browser_context.h" #include "content/public/test/test_renderer_host.h" +#include "content/public/test/test_web_contents_factory.h" #include "content/public/test/web_contents_tester.h" +#include "content/test/test_web_contents.h" #include "net/http/http_response_headers.h" +#include "services/network/test/test_network_context.h" #include "testing/gtest/include/gtest/gtest.h" namespace ipfs { +class FakeIPFSHostResolver : public ipfs::IPFSHostResolver { + public: + explicit FakeIPFSHostResolver(network::mojom::NetworkContext* context) + : ipfs::IPFSHostResolver(context) {} + ~FakeIPFSHostResolver() override = default; + 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(), dnslink_); + } + + bool resolve_called() const { return resolve_called_ == 1; } + + void SetDNSLinkToRespond(const std::string& dnslink) { dnslink_ = dnslink; } + + private: + int resolve_called_ = 0; + std::string dnslink_; +}; + class IpfsTabHelperUnitTest : public testing::Test { public: IpfsTabHelperUnitTest() @@ -31,33 +57,46 @@ class IpfsTabHelperUnitTest : public testing::Test { void SetUp() override { ASSERT_TRUE(profile_manager_.SetUp()); + test_network_context_ = std::make_unique(); profile_ = profile_manager_.CreateTestingProfile("TestProfile"); - web_contents_ = - content::WebContentsTester::CreateTestWebContents(profile(), nullptr); + web_contents_ = content::TestWebContents::Create(profile(), nullptr); + auto ipfs_host_resolver = + std::make_unique(test_network_context_.get()); + ipfs_host_resolver_ = ipfs_host_resolver.get(); ASSERT_TRUE(web_contents_.get()); ASSERT_TRUE( ipfs::IPFSTabHelper::MaybeCreateForWebContents(web_contents_.get())); + + ipfs_tab_helper()->SetResolverForTesting(std::move(ipfs_host_resolver)); SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_LOCAL); } - TestingProfile* profile() { return profile_; } void SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes type) { profile_->GetPrefs()->SetInteger(kIPFSResolveMethod, static_cast(type)); } + void SetAutoRedirectDNSLink(bool value) { + profile_->GetPrefs()->SetBoolean(kIPFSAutoRedirectDNSLink, value); + } ipfs::IPFSTabHelper* ipfs_tab_helper() { return ipfs::IPFSTabHelper::FromWebContents(web_contents_.get()); } - content::WebContents* web_contents() { return web_contents_.get(); } + ipfs::FakeIPFSHostResolver* ipfs_host_resolver() { + return ipfs_host_resolver_; + } + + content::TestWebContents* web_contents() { return web_contents_.get(); } private: content::BrowserTaskEnvironment task_environment_; content::RenderViewHostTestEnabler render_view_host_test_enabler_; TestingProfileManager profile_manager_; raw_ptr profile_ = nullptr; - std::unique_ptr web_contents_; + std::unique_ptr web_contents_; + std::unique_ptr test_network_context_; + FakeIPFSHostResolver* ipfs_host_resolver_; }; TEST_F(IpfsTabHelperUnitTest, CanResolveURLTest) { @@ -81,93 +120,183 @@ TEST_F(IpfsTabHelperUnitTest, CanResolveURLTest) { ASSERT_FALSE(helper->CanResolveURL(GURL("https://bafyb.ipfs.dweb.link/"))); } -TEST_F(IpfsTabHelperUnitTest, URLResolvingTest) { +TEST_F(IpfsTabHelperUnitTest, + TranslateUrlToIpns_When_HasDNSLinkRecord_AndXIPFSPathHeader) { + auto* helper = ipfs_tab_helper(); + ASSERT_TRUE(helper); + + web_contents()->NavigateAndCommit(GURL("https://brantly.eth/page?query#ref")); + helper->SetPageURLForTesting(GURL("https://brantly.eth/page?query#ref")); + + auto headers = net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK"); + headers->AddHeader("x-ipfs-path", "somevalue"); + + ipfs_host_resolver()->SetDNSLinkToRespond("/ipns/brantly.eth/"); + helper->MaybeCheckDNSLinkRecord(headers.get()); + + EXPECT_TRUE(ipfs_host_resolver()->resolve_called()); + ASSERT_EQ(GURL("ipns://brantly.eth/page?query#ref"), + helper->GetIPFSResolvedURL()); +} + +TEST_F(IpfsTabHelperUnitTest, + TranslateUrlToIpns_When_HasDNSLinkRecord_AndOriginalPageFails_400) { + auto* helper = ipfs_tab_helper(); + ASSERT_TRUE(helper); + + web_contents()->NavigateAndCommit(GURL("https://brantly.eth/page?query#ref")); + helper->SetPageURLForTesting(GURL("https://brantly.eth/page?query#ref")); + + auto headers = net::HttpResponseHeaders::TryToCreate("HTTP/1.1 400 Nan"); + ipfs_host_resolver()->SetDNSLinkToRespond("/ipns/brantly.eth/"); + helper->MaybeCheckDNSLinkRecord(headers.get()); + + EXPECT_FALSE(ipfs_host_resolver()->resolve_called()); + ASSERT_EQ(GURL(), helper->GetIPFSResolvedURL()); +} + +TEST_F(IpfsTabHelperUnitTest, + TranslateUrlToIpns_When_HasDNSLinkRecord_AndOriginalPageFails_500) { + auto* helper = ipfs_tab_helper(); + ASSERT_TRUE(helper); + + web_contents()->NavigateAndCommit(GURL("https://brantly.eth/page?query#ref")); + helper->SetPageURLForTesting(GURL("https://brantly.eth/page?query#ref")); + + auto headers = net::HttpResponseHeaders::TryToCreate( + "HTTP/1.1 500 Internal server error"); + ipfs_host_resolver()->SetDNSLinkToRespond("/ipns/brantly.eth/"); + helper->MaybeCheckDNSLinkRecord(headers.get()); + + EXPECT_TRUE(ipfs_host_resolver()->resolve_called()); + ASSERT_EQ(GURL("ipns://brantly.eth/page?query#ref"), + helper->GetIPFSResolvedURL()); +} + +TEST_F(IpfsTabHelperUnitTest, + TranslateUrlToIpns_When_HasDNSLinkRecord_AndOriginalPageFails_505) { + auto* helper = ipfs_tab_helper(); + ASSERT_TRUE(helper); + + web_contents()->NavigateAndCommit(GURL("https://brantly.eth/page?query#ref")); + helper->SetPageURLForTesting(GURL("https://brantly.eth/page?query#ref")); + + auto headers = net::HttpResponseHeaders::TryToCreate( + "HTTP/1.1 505 Version not supported"); + ipfs_host_resolver()->SetDNSLinkToRespond("/ipns/brantly.eth/"); + helper->MaybeCheckDNSLinkRecord(headers.get()); + + EXPECT_TRUE(ipfs_host_resolver()->resolve_called()); + ASSERT_EQ(GURL("ipns://brantly.eth/page?query#ref"), + helper->GetIPFSResolvedURL()); +} + +TEST_F(IpfsTabHelperUnitTest, + DoNotTranslateUrlToIpns_When_NoHeader_And_NoError) { + auto* helper = ipfs_tab_helper(); + ASSERT_TRUE(helper); + + web_contents()->NavigateAndCommit(GURL("https://brantly.eth/page?query#ref")); + helper->SetPageURLForTesting(GURL("https://brantly.eth/page?query#ref")); + + auto headers = net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK"); + + ipfs_host_resolver()->SetDNSLinkToRespond(""); + helper->MaybeCheckDNSLinkRecord(headers.get()); + + EXPECT_FALSE(ipfs_host_resolver()->resolve_called()); + ASSERT_EQ(GURL(), helper->GetIPFSResolvedURL()); +} + +TEST_F(IpfsTabHelperUnitTest, DNSLinkRecordResolved_AutoRedirectDNSLink) { auto* helper = ipfs_tab_helper(); ASSERT_TRUE(helper); GURL gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(), chrome::GetChannel()); + web_contents()->NavigateAndCommit(GURL("https://brantly.eth/page?query#ref")); - GURL test_url("ipns://brantly.eth/page?query#ref"); - helper->SetPageURLForTesting(test_url); - helper->IPFSLinkResolved(helper->ResolveDNSLinkURL(test_url)); - auto resolved_url = helper->GetIPFSResolvedURL(); - EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/brantly.eth/page"); - EXPECT_EQ(resolved_url.query(), "query"); - EXPECT_EQ(resolved_url.ref(), "ref"); + helper->SetPageURLForTesting(GURL("https://brantly.eth/page?query#ref")); + helper->HostResolvedCallback(absl::nullopt, "brantly.eth", + "/ipns/brantly.eth/"); + ASSERT_EQ(GURL("ipns://brantly.eth/page?query#ref"), + helper->GetIPFSResolvedURL()); +} - test_url = GURL("ipns://brantly.eth/"); - helper->SetPageURLForTesting(test_url); - helper->IPFSLinkResolved(helper->ResolveDNSLinkURL(test_url)); - resolved_url = helper->GetIPFSResolvedURL(); - EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/brantly.eth/"); - EXPECT_EQ(resolved_url.query(), ""); - EXPECT_EQ(resolved_url.ref(), ""); - - test_url = GURL("https://docs.ipfs.io/install/ipfs-desktop/?foo=bar#ref"); - helper->SetPageURLForTesting(test_url); - helper->IPFSLinkResolved(helper->ResolveDNSLinkURL(test_url)); - resolved_url = helper->GetIPFSResolvedURL(); - EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/ipns/docs.ipfs.io/install/ipfs-desktop/"); - EXPECT_EQ(resolved_url.query(), "foo=bar"); - EXPECT_EQ(resolved_url.ref(), "ref"); +TEST_F(IpfsTabHelperUnitTest, XIpfsPathHeaderUsed_IfNoDnsLinkRecord_IPFS) { + auto* helper = ipfs_tab_helper(); + ASSERT_TRUE(helper); + web_contents()->NavigateAndCommit(GURL("https://brantly.eth/page?query#ref")); + helper->SetPageURLForTesting(GURL("https://brantly.eth/page?query#ref")); SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY); - test_url = GURL("https://docs.ipfs.io/install/ipfs-desktop/?foo=bar#ref"); - gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(), - chrome::GetChannel()); - helper->SetPageURLForTesting(test_url); - helper->IPFSLinkResolved(helper->ResolveDNSLinkURL(test_url)); - resolved_url = helper->GetIPFSResolvedURL(); + GURL gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(), + chrome::GetChannel()); + + auto headers = net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK"); + headers->AddHeader("x-ipfs-path", "/ipfs/bafy"); + + ipfs_host_resolver()->SetDNSLinkToRespond(""); + helper->MaybeCheckDNSLinkRecord(headers.get()); + + EXPECT_TRUE(ipfs_host_resolver()->resolve_called()); + GURL resolved_url = helper->GetIPFSResolvedURL(); + EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/ipns/docs.ipfs.io/install/ipfs-desktop/"); - EXPECT_EQ(resolved_url.query(), "foo=bar"); + EXPECT_EQ(resolved_url.path(), "/ipfs/bafy"); + EXPECT_EQ(resolved_url.query(), "query"); EXPECT_EQ(resolved_url.ref(), "ref"); } -TEST_F(IpfsTabHelperUnitTest, ResolveDNSLinkURL) { +TEST_F(IpfsTabHelperUnitTest, XIpfsPathHeaderUsed_IfNoDnsLinkRecord_IPNS) { auto* helper = ipfs_tab_helper(); ASSERT_TRUE(helper); - SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_LOCAL); + + web_contents()->NavigateAndCommit(GURL("https://brantly.eth/page?query#ref")); + helper->SetPageURLForTesting(GURL("https://brantly.eth/page?query#ref")); + SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY); GURL gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(), chrome::GetChannel()); - GURL test_url("https://docs.ipfs.io/"); - auto resolved_url = helper->ResolveDNSLinkURL(test_url); - EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/ipns/docs.ipfs.io/"); + auto headers = net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK"); + headers->AddHeader("x-ipfs-path", "/ipns/brantly.eth/"); - SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY); - gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(), - chrome::GetChannel()); + ipfs_host_resolver()->SetDNSLinkToRespond(""); + helper->MaybeCheckDNSLinkRecord(headers.get()); - resolved_url = helper->ResolveDNSLinkURL(test_url); - EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/ipns/docs.ipfs.io/"); - - SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_LOCAL); - gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(), - chrome::GetChannel()); + EXPECT_TRUE(ipfs_host_resolver()->resolve_called()); + GURL resolved_url = helper->GetIPFSResolvedURL(); - test_url = GURL("https://docs.ipfs.io/install/ipfs-desktop/?foo=bar#qqqq"); - resolved_url = helper->ResolveDNSLinkURL(test_url); EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/ipns/docs.ipfs.io/install/ipfs-desktop/"); - EXPECT_TRUE(resolved_url.query().empty()); - EXPECT_TRUE(resolved_url.ref().empty()); + EXPECT_EQ(resolved_url.path(), "/ipns/brantly.eth/"); + EXPECT_EQ(resolved_url.query(), "query"); + EXPECT_EQ(resolved_url.ref(), "ref"); +} - SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY); - gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(), - chrome::GetChannel()); +TEST_F(IpfsTabHelperUnitTest, ResolveXIPFSPathUrl) { + auto* helper = ipfs_tab_helper(); + ASSERT_TRUE(helper); - test_url = GURL("https://docs.ipfs.io/install/ipfs-desktop/?foo=bar#qqqq"); - resolved_url = helper->ResolveDNSLinkURL(test_url); - EXPECT_EQ(resolved_url.host(), gateway.host()); - EXPECT_EQ(resolved_url.path(), "/ipns/docs.ipfs.io/install/ipfs-desktop/"); - EXPECT_TRUE(resolved_url.query().empty()); - EXPECT_TRUE(resolved_url.ref().empty()); + { + SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY); + GURL gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(), + chrome::GetChannel()); + GURL url = helper->ResolveXIPFSPathUrl("/ipfs/bafy"); + EXPECT_EQ(url.host(), gateway.host()); + EXPECT_EQ(url.path(), "/ipfs/bafy"); + EXPECT_EQ(url.query(), ""); + EXPECT_EQ(url.ref(), ""); + } + + { + SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_LOCAL); + GURL gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(), + chrome::GetChannel()); + GURL url = helper->ResolveXIPFSPathUrl("/ipfs/bafy"); + EXPECT_EQ(url.host(), gateway.host()); + EXPECT_EQ(url.path(), "/ipfs/bafy"); + EXPECT_EQ(url.query(), ""); + EXPECT_EQ(url.ref(), ""); + } } TEST_F(IpfsTabHelperUnitTest, GatewayResolving) { @@ -176,7 +305,7 @@ TEST_F(IpfsTabHelperUnitTest, GatewayResolving) { GURL api_server = GetAPIServer(chrome::GetChannel()); helper->SetPageURLForTesting(api_server); - helper->IPFSLinkResolved(GURL()); + helper->DNSLinkResolved(GURL()); ASSERT_FALSE(helper->GetIPFSResolvedURL().is_valid()); scoped_refptr response_headers( @@ -184,23 +313,25 @@ TEST_F(IpfsTabHelperUnitTest, GatewayResolving) { std::to_string(200))); response_headers->AddHeader("x-ipfs-path", "/ipfs/bafy"); - helper->MaybeShowDNSLinkButton(response_headers.get()); + + helper->MaybeCheckDNSLinkRecord(response_headers.get()); ASSERT_FALSE(helper->ipfs_resolved_url_.is_valid()); GURL test_url("ipns://brantly.eth/"); helper->SetPageURLForTesting(api_server); - helper->IPFSLinkResolved(test_url); - helper->MaybeShowDNSLinkButton(response_headers.get()); + helper->DNSLinkResolved(test_url); + + helper->MaybeCheckDNSLinkRecord(response_headers.get()); ASSERT_FALSE(helper->ipfs_resolved_url_.is_valid()); helper->SetPageURLForTesting(api_server); - helper->IPFSLinkResolved(test_url); + helper->DNSLinkResolved(test_url); helper->UpdateDnsLinkButtonState(); ASSERT_FALSE(helper->ipfs_resolved_url_.is_valid()); helper->SetPageURLForTesting(api_server); - helper->IPFSLinkResolved(GURL()); - helper->MaybeShowDNSLinkButton(response_headers.get()); + helper->DNSLinkResolved(GURL()); + helper->MaybeCheckDNSLinkRecord(response_headers.get()); ASSERT_FALSE(helper->ipfs_resolved_url_.is_valid()); }