Skip to content

Commit

Permalink
Resolve DNSLink as <gateway>/ipns/<dnslink-domain>
Browse files Browse the repository at this point in the history
  • Loading branch information
spylogsster committed Feb 1, 2022
1 parent e8510e1 commit e1ddca6
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 71 deletions.
84 changes: 30 additions & 54 deletions browser/ipfs/ipfs_tab_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,40 +37,6 @@ const char kDnsDomainPrefix[] = "_dnslink.";
// 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 @@ -172,23 +138,7 @@ GURL IPFSTabHelper::GetIPFSResolvedURL() const {
GURL::Replacements replacements;
replacements.SetQueryStr(current.query_piece());
replacements.SetRefStr(current.ref_piece());
std::string cid;
std::string path;
ipfs::ParseCIDAndPathFromIPFSUrl(ipfs_resolved_url_, &cid, &path);
auto resolved_scheme = ipfs_resolved_url_.scheme();
std::string resolved_path = current.path();
std::vector<std::string> parts = base::SplitString(
current.path(), "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
// If public gateway like https://ipfs.io/ipfs/{cid}/..
// or for IPNS like ipns://branty.eth/path/..
// skip duplication for /{scheme}/{cid}/ and add the rest parts
if (parts.size() >= 3 && parts[2] == cid) {
parts.erase(parts.begin() + 1, parts.begin() + 3);
resolved_path = base::JoinString(parts, "/");
}
std::string current_ipfs_url = resolved_scheme + "://" + cid + resolved_path;
GURL resolved_url(current_ipfs_url);
return resolved_url.ReplaceComponents(replacements);
return ipfs_resolved_url_.ReplaceComponents(replacements);
}

void IPFSTabHelper::ResolveIPFSLink() {
Expand Down Expand Up @@ -238,15 +188,40 @@ bool IPFSTabHelper::CanResolveURL(const GURL& url) const {
!IsAPIGateway(url_origin.GetURL(), chrome::GetChannel());
if (!IsLocalGatewayConfigured(pref_service_)) {
resolve = resolve && !IsDefaultGatewayURL(url, pref_service_);
} else {
resolve = resolve && !IsLocalGatewayURL(url);
}
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
// <gateway>/ipns/<dnslink-domain>/<dnslink-path>
GURL IPFSTabHelper::ResolveDNSLinkURL(GURL url) {
if (!url.is_valid())
return url;
GURL gateway =
ipfs::GetConfiguredBaseGateway(pref_service_, chrome::GetChannel());
GURL::Replacements replacements;
auto path = GetPathForDNSLink(url);
replacements.SetPathStr(path);
return gateway.ReplaceComponents(replacements);
}

void IPFSTabHelper::MaybeShowDNSLinkButton(
const net::HttpResponseHeaders* headers) {
UpdateDnsLinkButtonState();
auto current_url = GetCurrentPageURL();
if (!IsDNSLinkCheckEnabled() || !headers || ipfs_resolved_url_.is_valid() ||
!CanResolveURL(GetCurrentPageURL()))
!CanResolveURL(current_url))
return;

int response_code = headers->response_code();
Expand All @@ -255,9 +230,10 @@ void IPFSTabHelper::MaybeShowDNSLinkButton(
ResolveIPFSLink();
} else if (headers->HasHeader(kIfpsPathHeader)) {
std::string ipfs_path_value;
if (!headers->GetNormalizedHeader(kIfpsPathHeader, &ipfs_path_value))
if (!headers->GetNormalizedHeader(kIfpsPathHeader, &ipfs_path_value) ||
ipfs_path_value.empty())
return;
GURL resolved_url = ParseURLFromHeader(ipfs_path_value);
auto resolved_url = ResolveDNSLinkURL(current_url);
if (resolved_url.is_valid())
IPFSLinkResolved(resolved_url);
}
Expand Down
3 changes: 3 additions & 0 deletions browser/ipfs/ipfs_tab_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class IPFSTabHelper : public content::WebContentsObserver,
FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, CanResolveURLTest);
FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, URLResolvingTest);
FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, GatewayResolving);
FRIEND_TEST_ALL_PREFIXES(IpfsTabHelperUnitTest, ResolveDNSLinkURL);

friend class content::WebContentsUserData<IPFSTabHelper>;
explicit IPFSTabHelper(content::WebContents* web_contents);
Expand All @@ -66,6 +67,7 @@ class IPFSTabHelper : public content::WebContentsObserver,
void IPFSLinkResolved(const GURL& ipfs);
void MaybeShowDNSLinkButton(const net::HttpResponseHeaders* headers);
void UpdateDnsLinkButtonState();
GURL ResolveDNSLinkURL(GURL url);

void MaybeSetupIpfsProtocolHandlers(const GURL& url);

Expand All @@ -75,6 +77,7 @@ class IPFSTabHelper : public content::WebContentsObserver,
void UpdateLocationBar();

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

Expand Down
97 changes: 80 additions & 17 deletions browser/ipfs/ipfs_tab_helper_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ class IpfsTabHelperUnitTest : public testing::Test {
ASSERT_TRUE(web_contents_.get());
ASSERT_TRUE(
ipfs::IPFSTabHelper::MaybeCreateForWebContents(web_contents_.get()));
profile_->GetPrefs()->SetInteger(
kIPFSResolveMethod,
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_LOCAL));
SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_LOCAL);
}

TestingProfile* profile() { return profile_; }
void SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes type) {
profile_->GetPrefs()->SetInteger(kIPFSResolveMethod,
static_cast<int>(type));
}

ipfs::IPFSTabHelper* ipfs_tab_helper() {
return ipfs::IPFSTabHelper::FromWebContents(web_contents_.get());
Expand Down Expand Up @@ -82,29 +84,90 @@ TEST_F(IpfsTabHelperUnitTest, CanResolveURLTest) {
TEST_F(IpfsTabHelperUnitTest, URLResolvingTest) {
auto* helper = ipfs_tab_helper();
ASSERT_TRUE(helper);
GURL gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(),
chrome::GetChannel());

GURL test_url("ipns://brantly.eth/page?query#ref");
helper->SetPageURLForTesting(test_url);
helper->IPFSLinkResolved(GURL("ipns://brantly.eth/"));
EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), test_url.spec());
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");

test_url = GURL("ipns://brantly.eth/");
helper->SetPageURLForTesting(test_url);
helper->IPFSLinkResolved(GURL("ipns://brantly.eth/"));
EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), test_url.spec());

test_url = GURL("https://ipfs.io/ipfs/bafy/wiki/empty.html?query#ref");
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(GURL("ipfs://bafy"));

EXPECT_EQ(helper->GetIPFSResolvedURL().spec(),
"ipfs://bafy/wiki/empty.html?query#ref");

test_url = GURL("https://ipfs.io/ipfs/bafy");
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");

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(GURL("ipfs://bafy"));
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");
}

EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), "ipfs://bafy");
TEST_F(IpfsTabHelperUnitTest, ResolveDNSLinkURL) {
auto* helper = ipfs_tab_helper();
ASSERT_TRUE(helper);
SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_LOCAL);
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/");

SetIPFSResolveMethodPref(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY);
gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(),
chrome::GetChannel());

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());

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);
gateway = ipfs::GetConfiguredBaseGateway(profile()->GetPrefs(),
chrome::GetChannel());

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());
}

TEST_F(IpfsTabHelperUnitTest, GatewayResolving) {
Expand Down

0 comments on commit e1ddca6

Please sign in to comment.