Skip to content

Commit

Permalink
Redirect gateway-like urls to ipfs://
Browse files Browse the repository at this point in the history
Resolves brave/brave-browser#21454
Urls in format of https://bafy.ipfs.gateway.io or https://gateway.io/ipfs/bafy
are now redirected to ipfs:// scheme if x-ipfs-path header is received
  • Loading branch information
cypt4 committed Oct 14, 2022
1 parent 8db01cb commit 8826a71
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 47 deletions.
170 changes: 151 additions & 19 deletions browser/ipfs/test/ipfs_service_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,20 @@ class IpfsServiceBrowserTest : public InProcessBrowserTest {
return http_response;
}

std::unique_ptr<net::test_server::HttpResponse> HandlePublicGatewayRequest(
const net::test_server::HttpRequest& request) {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_content_type("text/html");

// IPFS gateways set this
http_response->AddCustomHeader("access-control-allow-origin", "*");
http_response->AddCustomHeader("x-ipfs-path", "/ipfs/Qmm");
http_response->set_code(net::HTTP_OK);

return http_response;
}

std::unique_ptr<net::test_server::HttpResponse> HandleEmbeddedSrvrRequest(
const net::test_server::HttpRequest& request) {
auto http_response =
Expand Down Expand Up @@ -1053,57 +1067,175 @@ IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest, CannotLoadIPFSImageFromHTTP) {
EXPECT_EQ(base::Value(true), loaded.value);
}

IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest, TopLevelAutoRedirectsOn) {
IN_PROC_BROWSER_TEST_F(
IpfsServiceBrowserTest,
TopLevelAutoRedirectsOn_Gateway_RedirectFromGatewayLikeUrl_IpfsSubDomain) {
ResetTestServer(
base::BindRepeating(&IpfsServiceBrowserTest::HandleEmbeddedSrvrRequest,
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
base::Unretained(this)));
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
browser()->profile()->GetPrefs()->SetInteger(
kIPFSResolveMethod,
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY));
GURL gateway = GetURL("b.com", "/");
SetIPFSDefaultGatewayForTest(gateway);
auto tab_url = GetURL("a.com", "/simple.html");

auto tab_url = GetURL(
"bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.a.com",
"/simple.html?a=b");

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(contents->GetURL().host(), tab_url.host());
EXPECT_EQ(
contents->GetURL(),
GetURL(
"b.com",
"/ipfs/bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
"simple.html?a=b"));
}

IN_PROC_BROWSER_TEST_F(
IpfsServiceBrowserTest,
TopLevelAutoRedirectsOn_Gateway_RedirectFromGatewayLikeUrl_IpfsPath) {
ResetTestServer(
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
base::Unretained(this)));
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
browser()->profile()->GetPrefs()->SetInteger(
kIPFSResolveMethod,
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY));
tab_url = GURL("ipfs://Qmc2JTQo4iXf24g98otZmGFQq176eQ2Cdbb88qA5ToMEvC/2");
GURL gateway = GetURL("b.com", "/");
SetIPFSDefaultGatewayForTest(gateway);

auto tab_url = GetURL(
"a.com",
"/ipfs/bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
"simple.html?a=b");

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
auto domain = GetDomainAndRegistry(
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(
contents->GetURL(),
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
GetURL(
"b.com",
"/ipfs/bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
"simple.html?a=b"));
}

EXPECT_EQ(domain, gateway.host());
IN_PROC_BROWSER_TEST_F(
IpfsServiceBrowserTest,
TopLevelAutoRedirectsOn_ASK_RedirectFromGatewayLikeUrl_IpfsPath) {
ResetTestServer(
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
base::Unretained(this)));
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
browser()->profile()->GetPrefs()->SetInteger(
kIPFSResolveMethod,
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_ASK));
GURL gateway = GetURL("b.com", "/");
SetIPFSDefaultGatewayForTest(gateway);

auto tab_url = GetURL(
"a.com",
"/ipfs/bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
"simple.html?a=b");

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(
contents->GetURL(),
GURL("ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
"simple.html?a=b"));
}

IN_PROC_BROWSER_TEST_F(
IpfsServiceBrowserTest,
TopLevelAutoRedirectsOn_ASK_RedirectFromGatewayLikeUrl_IpfsSubDomain) {
ResetTestServer(
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
base::Unretained(this)));
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
browser()->profile()->GetPrefs()->SetInteger(
kIPFSResolveMethod,
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_ASK));
GURL gateway = GetURL("b.com", "/");
SetIPFSDefaultGatewayForTest(gateway);

auto tab_url = GetURL(
"bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.a.com",
"/simple.html?a=b");

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(
contents->GetURL(),
GURL("ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
"simple.html?a=b"));
}

IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest,
TopLevelAutoRedirectsOnWithQuery) {
TopLevelAutoRedirectsOn_DoNotTranslateSimpleUrls) {
ResetTestServer(
base::BindRepeating(&IpfsServiceBrowserTest::HandleEmbeddedSrvrRequest,
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
base::Unretained(this)));
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
browser()->profile()->GetPrefs()->SetInteger(
kIPFSResolveMethod,
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_ASK));
GURL gateway = GetURL("b.com", "/");
SetIPFSDefaultGatewayForTest(gateway);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GetURL("a.com", "/simple.html?abc=123xyz&other=qwerty")));

auto tab_url = GetURL("a.com", "/simple.html?a=b");

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(contents->GetURL().query(), "abc=123xyz&other=qwerty");
EXPECT_EQ(contents->GetURL(), tab_url);
}

IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest,
TopLevelAutoRedirectsOn_DoNotTranslateIncompleteUrls) {
ResetTestServer(
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
base::Unretained(this)));
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
browser()->profile()->GetPrefs()->SetInteger(
kIPFSResolveMethod,
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_ASK));
GURL gateway = GetURL("b.com", "/");
SetIPFSDefaultGatewayForTest(gateway);

auto tab_url = GetURL("ipfs.a.com", "/simple.html?a=b");

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(contents->GetURL(), tab_url);
}

IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest, TopLevelAutoRedirectsOff) {
ResetTestServer(
base::BindRepeating(&IpfsServiceBrowserTest::HandleEmbeddedSrvrRequest,
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
base::Unretained(this)));
SetIPFSDefaultGatewayForTest(GetURL("b.com", "/"));
GURL other_gateway = GetURL("a.com", "/simple.html");
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GetURL("a.com", "/simple.html")));
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, false);
browser()->profile()->GetPrefs()->SetInteger(
kIPFSResolveMethod,
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_ASK));
GURL gateway = GetURL("b.com", "/");
SetIPFSDefaultGatewayForTest(gateway);

auto tab_url = GetURL(
"bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.a.com",
"/simple.html?a=b");

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(contents->GetURL().host(), other_gateway.host());
EXPECT_EQ(contents->GetURL(), tab_url);
}

IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest, ImportTextToIpfs) {
Expand Down
35 changes: 17 additions & 18 deletions browser/net/ipfs_redirect_network_delegate_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ int OnHeadersReceived_IPFSRedirectWork(
std::shared_ptr<brave::BraveRequestInfo> ctx) {
if (!ctx->browser_context)
return net::OK;

if (ctx->resource_type == blink::mojom::ResourceType::kSubFrame) {
return net::OK;
}

auto* prefs = user_prefs::UserPrefs::Get(ctx->browser_context);
if (IsIpfsResolveMethodDisabled(prefs)) {
return net::OK;
Expand All @@ -104,25 +109,19 @@ int OnHeadersReceived_IPFSRedirectWork(
if (ctx->ipfs_auto_fallback && !api_gateway && response_headers &&
response_headers->GetNormalizedHeader("x-ipfs-path", &ipfs_path) &&
// Make sure we don't infinite redirect
!ctx->request_url.DomainIs(ctx->ipfs_gateway_url.host()) &&
// Do not redirect if the frame is not ipfs/ipns
IsIPFSScheme(ctx->initiator_url)) {
GURL::Replacements replacements;
replacements.SetPathStr(ipfs_path);

if (ctx->request_url.has_query()) {
replacements.SetQueryStr(ctx->request_url.query_piece());
!ctx->request_url.DomainIs(ctx->ipfs_gateway_url.host())) {
auto translated_url = ipfs::TranslateToCurrentGatewayUrl(ctx->request_url);

if (translated_url) {
*override_response_headers =
new net::HttpResponseHeaders(response_headers->raw_headers());
(*override_response_headers)
->ReplaceStatusLine("HTTP/1.1 307 Temporary Redirect");
(*override_response_headers)->RemoveHeader("Location");
(*override_response_headers)
->AddHeader("Location", translated_url.value().spec());
*allowed_unsafe_redirect_url = translated_url.value();
}

GURL new_url = ctx->ipfs_gateway_url.ReplaceComponents(replacements);

*override_response_headers =
new net::HttpResponseHeaders(response_headers->raw_headers());
(*override_response_headers)
->ReplaceStatusLine("HTTP/1.1 307 Temporary Redirect");
(*override_response_headers)->RemoveHeader("Location");
(*override_response_headers)->AddHeader("Location", new_url.spec());
*allowed_unsafe_redirect_url = new_url;
}

return net::OK;
Expand Down
19 changes: 9 additions & 10 deletions browser/net/ipfs_redirect_network_delegate_helper_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ TEST_F(IPFSRedirectNetworkDelegateHelperTest, TranslateIPFSURIIPNSScheme) {
TEST_F(IPFSRedirectNetworkDelegateHelperTest, HeadersIPFSWorkWithRedirect) {
GURL url(
"https://cloudflare-ipfs.com/ipfs/"
"QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd");
"QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd/path/?a=b");
auto request_info = std::make_shared<brave::BraveRequestInfo>(url);
request_info->browser_context = profile();
request_info->ipfs_gateway_url = GetPublicGateway();
Expand All @@ -312,16 +312,15 @@ TEST_F(IPFSRedirectNetworkDelegateHelperTest, HeadersIPFSWorkWithRedirect) {
std::string location;
EXPECT_TRUE(overwrite_response_headers->EnumerateHeader(nullptr, "Location",
&location));
GURL converted_url = GURL("https://dweb.link/test");
EXPECT_EQ(location, converted_url);
GURL converted_url =
GURL("ipfs://QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd/path?a=b");
EXPECT_EQ(GURL(location), converted_url);
EXPECT_EQ(allowed_unsafe_redirect_url, converted_url);
}

TEST_F(IPFSRedirectNetworkDelegateHelperTest,
HeadersIPFSWorkWithNoRedirectHttps) {
GURL url(
"https://cloudflare-ipfs.com/ipfs/"
"QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd");
HeadersIPFSWorkWithNoRedirect_NoHeader) {
GURL url("https://brave.com");
auto request_info = std::make_shared<brave::BraveRequestInfo>(url);
request_info->browser_context = profile();
request_info->ipfs_gateway_url = GetPublicGateway();
Expand All @@ -333,10 +332,10 @@ TEST_F(IPFSRedirectNetworkDelegateHelperTest,

scoped_refptr<net::HttpResponseHeaders> orig_response_headers =
new net::HttpResponseHeaders(std::string());
orig_response_headers->AddHeader("x-ipfs-path", "/test");
scoped_refptr<net::HttpResponseHeaders> overwrite_response_headers =
new net::HttpResponseHeaders(std::string());
GURL allowed_unsafe_redirect_url;
orig_response_headers->AddHeader("x-ipfs-path", "/test");

int rc = ipfs::OnHeadersReceived_IPFSRedirectWork(
orig_response_headers.get(), &overwrite_response_headers,
Expand All @@ -351,7 +350,8 @@ TEST_F(IPFSRedirectNetworkDelegateHelperTest,
EXPECT_TRUE(allowed_unsafe_redirect_url.is_empty());
}

TEST_F(IPFSRedirectNetworkDelegateHelperTest, HeadersIPFSWorkNoRedirect) {
TEST_F(IPFSRedirectNetworkDelegateHelperTest,
HeadersIPFSWorkNoRedirect_WrongFormat) {
GURL url(
"https://cloudflare-ipfs.com/ipfs/"
"QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd");
Expand All @@ -365,7 +365,6 @@ TEST_F(IPFSRedirectNetworkDelegateHelperTest, HeadersIPFSWorkNoRedirect) {

scoped_refptr<net::HttpResponseHeaders> orig_response_headers =
new net::HttpResponseHeaders(std::string());
orig_response_headers->AddHeader("x-ipfs-path", "/test");
scoped_refptr<net::HttpResponseHeaders> overwrite_response_headers =
new net::HttpResponseHeaders(std::string());
GURL allowed_unsafe_redirect_url;
Expand Down
37 changes: 37 additions & 0 deletions components/ipfs/ipfs_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -445,4 +445,41 @@ std::string GetRegistryDomainFromIPNS(const GURL& url) {
cid, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
}

absl::optional<GURL> TranslateToCurrentGatewayUrl(const GURL& url) {
if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) {
return absl::nullopt;
}

std::vector<std::string> host_parts = base::SplitStringUsingSubstr(
url.host(), ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);

if (host_parts.size() > 2 && IsValidCID(host_parts.at(0)) &&
host_parts.at(1) == "ipfs") {
GURL final_url = GURL("ipfs://" + host_parts.at(0) + url.path());
GURL::Replacements replacements;
replacements.SetQueryStr(url.query_piece());
return final_url.ReplaceComponents(replacements);
}

std::vector<std::string> path_parts = base::SplitStringUsingSubstr(
url.path(), "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);

if (path_parts.size() >= 2 && path_parts.at(0) == "ipfs" &&
IsValidCID(path_parts.at(1))) {
std::string final_path;
if (path_parts.size() >= 3) {
std::vector<std::string> final_path_parts(path_parts.begin() + 2,
path_parts.end());
final_path = "/" + base::JoinString(final_path_parts, "/");
}

GURL final_url = GURL("ipfs://" + path_parts.at(1) + final_path);
GURL::Replacements replacements;
replacements.SetQueryStr(url.query_piece());
return final_url.ReplaceComponents(replacements);
}

return absl::nullopt;
}

} // namespace ipfs
1 change: 1 addition & 0 deletions components/ipfs/ipfs_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ bool IsIpfsResolveMethodDisabled(PrefService* prefs);
bool IsIpfsResolveMethodAsk(PrefService* prefs);
std::string GetRegistryDomainFromIPNS(const GURL& url);
bool IsValidCIDOrDomain(const std::string& value);
absl::optional<GURL> TranslateToCurrentGatewayUrl(const GURL& url);

} // namespace ipfs

Expand Down
Loading

0 comments on commit 8826a71

Please sign in to comment.