Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve ipfs links through x-ipfs-path and dnslink values #8254

Merged
merged 1 commit into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions browser/ipfs/ipfs_host_resolver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ void IPFSHostResolver::Resolve(const net::HostPortPair& host,
return;

if (host.host() == resolving_host_) {
if (callback && has_dnslink_) {
std::move(callback).Run(host.host());
if (callback) {
std::move(callback).Run(host.host(), dnslink_);
}
return;
}
Expand All @@ -68,7 +68,7 @@ void IPFSHostResolver::Resolve(const net::HostPortPair& host,

receiver_.reset();
resolved_callback_ = std::move(callback);
has_dnslink_ = false;
dnslink_.erase();
resolving_host_ = host.host();
net::HostPortPair local_host_port(prefix_ + resolving_host_, host.port());

Expand All @@ -92,15 +92,10 @@ void IPFSHostResolver::OnComplete(
void IPFSHostResolver::OnTextResults(const std::vector<std::string>& 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;
dnslink_ = GetDNSRecordValue(results, kDnsLinkHeader);

if (resolved_callback_)
std::move(resolved_callback_).Run(resolving_host_);
std::move(resolved_callback_).Run(resolving_host_, dnslink_);
}

} // namespace ipfs
7 changes: 5 additions & 2 deletions browser/ipfs/ipfs_host_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ class IPFSHostResolver : public network::ResolveHostClientBase {
~IPFSHostResolver() override;

using HostTextResultsCallback =
base::OnceCallback<void(const std::string& host)>;
base::OnceCallback<void(const std::string& host,
const std::string& dnslink)>;

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_; }
std::string dnslink() const { return dnslink_; }

void SetCompleteCallbackForTesting(base::OnceClosure complete_callback) {
complete_callback_for_testing_ = std::move(complete_callback);
Expand All @@ -52,7 +54,8 @@ class IPFSHostResolver : public network::ResolveHostClientBase {

std::string resolving_host_;
std::string prefix_;
bool has_dnslink_ = false;
std::string dnslink_;

network::mojom::NetworkContext* network_context_ = nullptr;
HostTextResultsCallback resolved_callback_;
base::OnceClosure complete_callback_for_testing_;
Expand Down
16 changes: 11 additions & 5 deletions browser/ipfs/ipfs_host_resolver_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ class IPFSHostResolverTest : public testing::Test {

void HostResolvedCallback(base::OnceClosure callback,
const std::string& expected_host,
const std::string& host) {
const std::string& host,
const std::string& dnslink) {
EXPECT_EQ(expected_host, host);
resolved_callback_called_++;
if (callback)
Expand Down Expand Up @@ -179,6 +180,7 @@ TEST_F(IPFSHostResolverTest, PrefixRunSuccess) {

run_loop.Run();
EXPECT_EQ(ipfs_resolver.host(), host);
EXPECT_EQ(ipfs_resolver.dnslink(), "abc");
EXPECT_EQ(fake_host_resolver_raw->resolve_host_called(), 1);
EXPECT_EQ(resolved_callback_called(), 1);
}
Expand Down Expand Up @@ -207,19 +209,20 @@ TEST_F(IPFSHostResolverTest, SuccessOnReuse) {

run_loop.Run();
EXPECT_EQ(ipfs_resolver.host(), host);
EXPECT_EQ(ipfs_resolver.dnslink(), "abc");
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);
},
[](const std::string& expected_host, const std::string& host,
const std::string& dnslink) { EXPECT_EQ(expected_host, host); },
host));
EXPECT_EQ(fake_host_resolver_raw->resolve_host_called(), 1);
EXPECT_EQ(resolved_callback_called(), 1);
EXPECT_EQ(ipfs_resolver.dnslink(), "abc");
}

TEST_F(IPFSHostResolverTest, ResolutionFailed) {
Expand All @@ -236,9 +239,12 @@ TEST_F(IPFSHostResolverTest, ResolutionFailed) {
ipfs_resolver.Resolve(
net::HostPortPair(host, 11), net::NetworkIsolationKey(),
net::DnsQueryType::TXT,
base::BindOnce([](const std::string& host) { NOTREACHED(); }));
base::BindOnce([](const std::string& host, const std::string& dnslink) {
NOTREACHED();
}));
run_loop.Run();
EXPECT_EQ(ipfs_resolver.host(), host);
EXPECT_EQ(ipfs_resolver.dnslink(), "");
EXPECT_EQ(fake_host_resolver_raw->resolve_host_called(), 1);
EXPECT_EQ(resolved_callback_called(), 0);
}
85 changes: 69 additions & 16 deletions browser/ipfs/ipfs_tab_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "base/containers/contains.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_split.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"
Expand All @@ -40,6 +41,44 @@ namespace {
// https://dnslink.io/#can-i-use-dnslink-in-non-dns-systems
const char kDnsDomainPrefix[] = "_dnslink.";

// IPFS HTTP gateways can return an x-ipfs-path header with each response.
// The value of the header is the IPFS path of the returned payload.
const char kIfpsPathHeader[] = "x-ipfs-path";

// /ipfs/{cid}/path → ipfs://{cid}/path
// query and fragment are taken from source page url
GURL ParseURLFromHeader(const std::string& value) {
if (value.empty())
return GURL();
std::vector<std::string> parts = base::SplitString(
value, "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
// Default length of header is /[scheme]/cid so we have 3 parts after split.
const int minimalPartsRequired = 3;
if (parts.size() < minimalPartsRequired || !parts.front().empty())
return GURL();
std::string scheme = parts[1];
if (scheme != ipfs::kIPFSScheme && scheme != ipfs::kIPNSScheme)
return GURL();
std::string cid = parts[2];
if (scheme.empty() || cid.empty())
return GURL();
std::string path;
// Add all other parts to url path.
if (parts.size() > minimalPartsRequired) {
for (size_t i = minimalPartsRequired; i < parts.size(); i++) {
if (parts[i].empty())
continue;
if (!path.empty())
path += "/";
path += parts[i];
}
}
std::string spec = scheme + "://" + cid;
if (!path.empty())
spec += "/" + path;
return GURL(spec);
}

// Sets current executable as default protocol handler in a system.
void SetupIPFSProtocolHandler(const std::string& protocol) {
auto isDefaultCallback = [](const std::string& protocol,
Expand Down Expand Up @@ -94,8 +133,8 @@ bool IPFSTabHelper::MaybeCreateForWebContents(
return true;
}

void IPFSTabHelper::DNSLinkHostResolved(const std::string& host) {
ipfs_resolved_host_ = host;
void IPFSTabHelper::IPFSLinkResolved(const GURL& ipfs) {
ipfs_resolved_url_ = ipfs;
if (pref_service_->GetBoolean(kIPFSAutoRedirectDNSLink)) {
content::OpenURLParams params(GetIPFSResolvedURL(), content::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
Expand All @@ -106,11 +145,18 @@ void IPFSTabHelper::DNSLinkHostResolved(const std::string& host) {
UpdateLocationBar();
}

void IPFSTabHelper::HostResolvedCallback(const std::string& host) {
void IPFSTabHelper::HostResolvedCallback(const std::string& host,
const std::string& dnslink) {
GURL current = web_contents()->GetURL();
if (current.host() != host || !current.SchemeIsHTTPOrHTTPS())
return;
DNSLinkHostResolved(host);
if (dnslink.empty())
return;
GURL::Replacements replacements;
replacements.SetSchemeStr(kIPNSScheme);
GURL resolved_url(current.ReplaceComponents(replacements));
if (resolved_url.is_valid())
IPFSLinkResolved(resolved_url);
}

void IPFSTabHelper::UpdateLocationBar() {
Expand All @@ -120,17 +166,18 @@ void IPFSTabHelper::UpdateLocationBar() {
}

GURL IPFSTabHelper::GetIPFSResolvedURL() const {
if (ipfs_resolved_host_.empty())
if (!ipfs_resolved_url_.is_valid())
return GURL();
GURL current = web_contents()->GetURL();
GURL::Replacements replacements;
replacements.SetSchemeStr(kIPNSScheme);
return current.ReplaceComponents(replacements);
replacements.SetQueryStr(current.query_piece());
replacements.SetRefStr(current.ref_piece());
return ipfs_resolved_url_.ReplaceComponents(replacements);
}

void IPFSTabHelper::ResolveIPFSLink() {
GURL current = web_contents()->GetURL();
if (!current.SchemeIsHTTPOrHTTPS() || ipfs_resolved_host_ == current.host())
if (!current.SchemeIsHTTPOrHTTPS())
return;

const auto& host_port_pair = net::HostPortPair::FromURL(current);
Expand All @@ -155,16 +202,16 @@ bool IPFSTabHelper::IsDNSLinkCheckEnabled() const {

void IPFSTabHelper::UpdateDnsLinkButtonState() {
if (!IsDNSLinkCheckEnabled()) {
if (!ipfs_resolved_host_.empty()) {
ipfs_resolved_host_.erase();
if (ipfs_resolved_url_.is_valid()) {
ipfs_resolved_url_ = GURL();
UpdateLocationBar();
}
return;
}

GURL current = web_contents()->GetURL();
if (!ipfs_resolved_host_.empty() && resolver_->host() != current.host()) {
ipfs_resolved_host_.erase();
if (ipfs_resolved_url_.is_valid() && resolver_->host() != current.host()) {
ipfs_resolved_url_ = GURL();
UpdateLocationBar();
}
}
Expand All @@ -174,15 +221,21 @@ void IPFSTabHelper::MaybeShowDNSLinkButton(content::NavigationHandle* handle) {
if (!IsDNSLinkCheckEnabled() || !handle->GetResponseHeaders())
return;
GURL current = web_contents()->GetURL();
if (!ipfs_resolved_host_.empty() || !current.SchemeIsHTTPOrHTTPS() ||
if (ipfs_resolved_url_.is_valid() || !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());
} else if (handle->GetResponseHeaders()->HasHeader(kIfpsPathHeader)) {
std::string ipfs_path_value;
if (!handle->GetResponseHeaders()->GetNormalizedHeader(kIfpsPathHeader,
&ipfs_path_value))
return;
GURL resolved_url = ParseURLFromHeader(ipfs_path_value);
if (resolved_url.is_valid())
IPFSLinkResolved(resolved_url);
}
}

Expand All @@ -208,7 +261,7 @@ void IPFSTabHelper::DidFinishNavigation(content::NavigationHandle* handle) {
return;
}
if (handle->GetResponseHeaders() &&
handle->GetResponseHeaders()->HasHeader("x-ipfs-path")) {
handle->GetResponseHeaders()->HasHeader(kIfpsPathHeader)) {
MaybeSetupIpfsProtocolHandlers(handle->GetURL());
}
MaybeShowDNSLinkButton(handle);
Expand Down
7 changes: 4 additions & 3 deletions browser/ipfs/ipfs_tab_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class IPFSTabHelper : public content::WebContentsObserver,
explicit IPFSTabHelper(content::WebContents* web_contents);

bool IsDNSLinkCheckEnabled() const;
void DNSLinkHostResolved(const std::string& host);
void IPFSLinkResolved(const GURL& ipfs);
void MaybeShowDNSLinkButton(content::NavigationHandle* handle);
void UpdateDnsLinkButtonState();

Expand All @@ -60,11 +60,12 @@ class IPFSTabHelper : public content::WebContentsObserver,
void UpdateLocationBar();

void ResolveIPFSLink();
void HostResolvedCallback(const std::string& host);
void HostResolvedCallback(const std::string& host,
const std::string& dnslink);

PrefService* pref_service_ = nullptr;
PrefChangeRegistrar pref_change_registrar_;
std::string ipfs_resolved_host_;
GURL ipfs_resolved_url_;
std::unique_ptr<IPFSHostResolver> resolver_;
base::WeakPtrFactory<IPFSTabHelper> weak_ptr_factory_{this};
WEB_CONTENTS_USER_DATA_KEY_DECL();
Expand Down
Loading