Skip to content

Commit

Permalink
Resolve ipfs links through x-ipfs-path and dnslink values
Browse files Browse the repository at this point in the history
  • Loading branch information
spylogsster committed Mar 16, 2021
1 parent d6d5b16 commit 018e7ec
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 53 deletions.
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

0 comments on commit 018e7ec

Please sign in to comment.