Skip to content

Commit a160422

Browse files
committed
Redirect gateway-like urls to ipfs://
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
1 parent f9f455e commit a160422

7 files changed

+265
-47
lines changed

browser/ipfs/test/ipfs_service_browsertest.cc

+151-19
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,20 @@ class IpfsServiceBrowserTest : public InProcessBrowserTest {
395395
return http_response;
396396
}
397397

398+
std::unique_ptr<net::test_server::HttpResponse> HandlePublicGatewayRequest(
399+
const net::test_server::HttpRequest& request) {
400+
auto http_response =
401+
std::make_unique<net::test_server::BasicHttpResponse>();
402+
http_response->set_content_type("text/html");
403+
404+
// IPFS gateways set this
405+
http_response->AddCustomHeader("access-control-allow-origin", "*");
406+
http_response->AddCustomHeader("x-ipfs-path", "/ipfs/Qmm");
407+
http_response->set_code(net::HTTP_OK);
408+
409+
return http_response;
410+
}
411+
398412
std::unique_ptr<net::test_server::HttpResponse> HandleEmbeddedSrvrRequest(
399413
const net::test_server::HttpRequest& request) {
400414
auto http_response =
@@ -1053,57 +1067,175 @@ IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest, CannotLoadIPFSImageFromHTTP) {
10531067
EXPECT_EQ(base::Value(true), loaded.value);
10541068
}
10551069

1056-
IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest, TopLevelAutoRedirectsOn) {
1070+
IN_PROC_BROWSER_TEST_F(
1071+
IpfsServiceBrowserTest,
1072+
TopLevelAutoRedirectsOn_Gateway_RedirectFromGatewayLikeUrl_IpfsSubDomain) {
10571073
ResetTestServer(
1058-
base::BindRepeating(&IpfsServiceBrowserTest::HandleEmbeddedSrvrRequest,
1074+
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
10591075
base::Unretained(this)));
10601076
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
1077+
browser()->profile()->GetPrefs()->SetInteger(
1078+
kIPFSResolveMethod,
1079+
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY));
10611080
GURL gateway = GetURL("b.com", "/");
10621081
SetIPFSDefaultGatewayForTest(gateway);
1063-
auto tab_url = GetURL("a.com", "/simple.html");
1082+
1083+
auto tab_url = GetURL(
1084+
"bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.a.com",
1085+
"/simple.html?a=b");
1086+
10641087
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
10651088
content::WebContents* contents =
10661089
browser()->tab_strip_model()->GetActiveWebContents();
1067-
EXPECT_EQ(contents->GetURL().host(), tab_url.host());
1090+
EXPECT_EQ(
1091+
contents->GetURL(),
1092+
GetURL(
1093+
"b.com",
1094+
"/ipfs/bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
1095+
"simple.html?a=b"));
1096+
}
10681097

1098+
IN_PROC_BROWSER_TEST_F(
1099+
IpfsServiceBrowserTest,
1100+
TopLevelAutoRedirectsOn_Gateway_RedirectFromGatewayLikeUrl_IpfsPath) {
1101+
ResetTestServer(
1102+
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
1103+
base::Unretained(this)));
1104+
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
10691105
browser()->profile()->GetPrefs()->SetInteger(
10701106
kIPFSResolveMethod,
10711107
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_GATEWAY));
1072-
tab_url = GURL("ipfs://Qmc2JTQo4iXf24g98otZmGFQq176eQ2Cdbb88qA5ToMEvC/2");
1108+
GURL gateway = GetURL("b.com", "/");
1109+
SetIPFSDefaultGatewayForTest(gateway);
1110+
1111+
auto tab_url = GetURL(
1112+
"a.com",
1113+
"/ipfs/bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
1114+
"simple.html?a=b");
1115+
10731116
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
1074-
auto domain = GetDomainAndRegistry(
1117+
content::WebContents* contents =
1118+
browser()->tab_strip_model()->GetActiveWebContents();
1119+
EXPECT_EQ(
10751120
contents->GetURL(),
1076-
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
1121+
GetURL(
1122+
"b.com",
1123+
"/ipfs/bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
1124+
"simple.html?a=b"));
1125+
}
10771126

1078-
EXPECT_EQ(domain, gateway.host());
1127+
IN_PROC_BROWSER_TEST_F(
1128+
IpfsServiceBrowserTest,
1129+
TopLevelAutoRedirectsOn_ASK_RedirectFromGatewayLikeUrl_IpfsPath) {
1130+
ResetTestServer(
1131+
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
1132+
base::Unretained(this)));
1133+
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
1134+
browser()->profile()->GetPrefs()->SetInteger(
1135+
kIPFSResolveMethod,
1136+
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_ASK));
1137+
GURL gateway = GetURL("b.com", "/");
1138+
SetIPFSDefaultGatewayForTest(gateway);
1139+
1140+
auto tab_url = GetURL(
1141+
"a.com",
1142+
"/ipfs/bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
1143+
"simple.html?a=b");
1144+
1145+
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
1146+
content::WebContents* contents =
1147+
browser()->tab_strip_model()->GetActiveWebContents();
1148+
EXPECT_EQ(
1149+
contents->GetURL(),
1150+
GURL("ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
1151+
"simple.html?a=b"));
1152+
}
1153+
1154+
IN_PROC_BROWSER_TEST_F(
1155+
IpfsServiceBrowserTest,
1156+
TopLevelAutoRedirectsOn_ASK_RedirectFromGatewayLikeUrl_IpfsSubDomain) {
1157+
ResetTestServer(
1158+
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
1159+
base::Unretained(this)));
1160+
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
1161+
browser()->profile()->GetPrefs()->SetInteger(
1162+
kIPFSResolveMethod,
1163+
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_ASK));
1164+
GURL gateway = GetURL("b.com", "/");
1165+
SetIPFSDefaultGatewayForTest(gateway);
1166+
1167+
auto tab_url = GetURL(
1168+
"bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.a.com",
1169+
"/simple.html?a=b");
1170+
1171+
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
1172+
content::WebContents* contents =
1173+
browser()->tab_strip_model()->GetActiveWebContents();
1174+
EXPECT_EQ(
1175+
contents->GetURL(),
1176+
GURL("ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"
1177+
"simple.html?a=b"));
10791178
}
10801179

10811180
IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest,
1082-
TopLevelAutoRedirectsOnWithQuery) {
1181+
TopLevelAutoRedirectsOn_DoNotTranslateSimpleUrls) {
10831182
ResetTestServer(
1084-
base::BindRepeating(&IpfsServiceBrowserTest::HandleEmbeddedSrvrRequest,
1183+
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
10851184
base::Unretained(this)));
10861185
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
1186+
browser()->profile()->GetPrefs()->SetInteger(
1187+
kIPFSResolveMethod,
1188+
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_ASK));
10871189
GURL gateway = GetURL("b.com", "/");
10881190
SetIPFSDefaultGatewayForTest(gateway);
1089-
ASSERT_TRUE(ui_test_utils::NavigateToURL(
1090-
browser(), GetURL("a.com", "/simple.html?abc=123xyz&other=qwerty")));
1191+
1192+
auto tab_url = GetURL("a.com", "/simple.html?a=b");
1193+
1194+
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
10911195
content::WebContents* contents =
10921196
browser()->tab_strip_model()->GetActiveWebContents();
1093-
EXPECT_EQ(contents->GetURL().query(), "abc=123xyz&other=qwerty");
1197+
EXPECT_EQ(contents->GetURL(), tab_url);
1198+
}
1199+
1200+
IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest,
1201+
TopLevelAutoRedirectsOn_DoNotTranslateIncompleteUrls) {
1202+
ResetTestServer(
1203+
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
1204+
base::Unretained(this)));
1205+
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, true);
1206+
browser()->profile()->GetPrefs()->SetInteger(
1207+
kIPFSResolveMethod,
1208+
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_ASK));
1209+
GURL gateway = GetURL("b.com", "/");
1210+
SetIPFSDefaultGatewayForTest(gateway);
1211+
1212+
auto tab_url = GetURL("ipfs.a.com", "/simple.html?a=b");
1213+
1214+
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
1215+
content::WebContents* contents =
1216+
browser()->tab_strip_model()->GetActiveWebContents();
1217+
EXPECT_EQ(contents->GetURL(), tab_url);
10941218
}
10951219

10961220
IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest, TopLevelAutoRedirectsOff) {
10971221
ResetTestServer(
1098-
base::BindRepeating(&IpfsServiceBrowserTest::HandleEmbeddedSrvrRequest,
1222+
base::BindRepeating(&IpfsServiceBrowserTest::HandlePublicGatewayRequest,
10991223
base::Unretained(this)));
1100-
SetIPFSDefaultGatewayForTest(GetURL("b.com", "/"));
1101-
GURL other_gateway = GetURL("a.com", "/simple.html");
1102-
ASSERT_TRUE(
1103-
ui_test_utils::NavigateToURL(browser(), GetURL("a.com", "/simple.html")));
1224+
browser()->profile()->GetPrefs()->SetBoolean(kIPFSAutoRedirectGateway, false);
1225+
browser()->profile()->GetPrefs()->SetInteger(
1226+
kIPFSResolveMethod,
1227+
static_cast<int>(ipfs::IPFSResolveMethodTypes::IPFS_ASK));
1228+
GURL gateway = GetURL("b.com", "/");
1229+
SetIPFSDefaultGatewayForTest(gateway);
1230+
1231+
auto tab_url = GetURL(
1232+
"bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.a.com",
1233+
"/simple.html?a=b");
1234+
1235+
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), tab_url));
11041236
content::WebContents* contents =
11051237
browser()->tab_strip_model()->GetActiveWebContents();
1106-
EXPECT_EQ(contents->GetURL().host(), other_gateway.host());
1238+
EXPECT_EQ(contents->GetURL(), tab_url);
11071239
}
11081240

11091241
IN_PROC_BROWSER_TEST_F(IpfsServiceBrowserTest, ImportTextToIpfs) {

browser/net/ipfs_redirect_network_delegate_helper.cc

+17-18
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ int OnHeadersReceived_IPFSRedirectWork(
9494
std::shared_ptr<brave::BraveRequestInfo> ctx) {
9595
if (!ctx->browser_context)
9696
return net::OK;
97+
98+
if (ctx->resource_type == blink::mojom::ResourceType::kSubFrame) {
99+
return net::OK;
100+
}
101+
97102
auto* prefs = user_prefs::UserPrefs::Get(ctx->browser_context);
98103
if (IsIpfsResolveMethodDisabled(prefs)) {
99104
return net::OK;
@@ -104,25 +109,19 @@ int OnHeadersReceived_IPFSRedirectWork(
104109
if (ctx->ipfs_auto_fallback && !api_gateway && response_headers &&
105110
response_headers->GetNormalizedHeader("x-ipfs-path", &ipfs_path) &&
106111
// Make sure we don't infinite redirect
107-
!ctx->request_url.DomainIs(ctx->ipfs_gateway_url.host()) &&
108-
// Do not redirect if the frame is not ipfs/ipns
109-
IsIPFSScheme(ctx->initiator_url)) {
110-
GURL::Replacements replacements;
111-
replacements.SetPathStr(ipfs_path);
112-
113-
if (ctx->request_url.has_query()) {
114-
replacements.SetQueryStr(ctx->request_url.query_piece());
112+
!ctx->request_url.DomainIs(ctx->ipfs_gateway_url.host())) {
113+
auto translated_url = ipfs::TranslateToCurrentGatewayUrl(ctx->request_url);
114+
115+
if (translated_url) {
116+
*override_response_headers =
117+
new net::HttpResponseHeaders(response_headers->raw_headers());
118+
(*override_response_headers)
119+
->ReplaceStatusLine("HTTP/1.1 307 Temporary Redirect");
120+
(*override_response_headers)->RemoveHeader("Location");
121+
(*override_response_headers)
122+
->AddHeader("Location", translated_url.value().spec());
123+
*allowed_unsafe_redirect_url = translated_url.value();
115124
}
116-
117-
GURL new_url = ctx->ipfs_gateway_url.ReplaceComponents(replacements);
118-
119-
*override_response_headers =
120-
new net::HttpResponseHeaders(response_headers->raw_headers());
121-
(*override_response_headers)
122-
->ReplaceStatusLine("HTTP/1.1 307 Temporary Redirect");
123-
(*override_response_headers)->RemoveHeader("Location");
124-
(*override_response_headers)->AddHeader("Location", new_url.spec());
125-
*allowed_unsafe_redirect_url = new_url;
126125
}
127126

128127
return net::OK;

browser/net/ipfs_redirect_network_delegate_helper_unittest.cc

+9-10
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ TEST_F(IPFSRedirectNetworkDelegateHelperTest, TranslateIPFSURIIPNSScheme) {
286286
TEST_F(IPFSRedirectNetworkDelegateHelperTest, HeadersIPFSWorkWithRedirect) {
287287
GURL url(
288288
"https://cloudflare-ipfs.com/ipfs/"
289-
"QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd");
289+
"QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd/path/?a=b");
290290
auto request_info = std::make_shared<brave::BraveRequestInfo>(url);
291291
request_info->browser_context = profile();
292292
request_info->ipfs_gateway_url = GetPublicGateway();
@@ -312,16 +312,15 @@ TEST_F(IPFSRedirectNetworkDelegateHelperTest, HeadersIPFSWorkWithRedirect) {
312312
std::string location;
313313
EXPECT_TRUE(overwrite_response_headers->EnumerateHeader(nullptr, "Location",
314314
&location));
315-
GURL converted_url = GURL("https://dweb.link/test");
316-
EXPECT_EQ(location, converted_url);
315+
GURL converted_url =
316+
GURL("ipfs://QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd/path?a=b");
317+
EXPECT_EQ(GURL(location), converted_url);
317318
EXPECT_EQ(allowed_unsafe_redirect_url, converted_url);
318319
}
319320

320321
TEST_F(IPFSRedirectNetworkDelegateHelperTest,
321-
HeadersIPFSWorkWithNoRedirectHttps) {
322-
GURL url(
323-
"https://cloudflare-ipfs.com/ipfs/"
324-
"QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd");
322+
HeadersIPFSWorkWithNoRedirect_NoHeader) {
323+
GURL url("https://brave.com");
325324
auto request_info = std::make_shared<brave::BraveRequestInfo>(url);
326325
request_info->browser_context = profile();
327326
request_info->ipfs_gateway_url = GetPublicGateway();
@@ -333,10 +332,10 @@ TEST_F(IPFSRedirectNetworkDelegateHelperTest,
333332

334333
scoped_refptr<net::HttpResponseHeaders> orig_response_headers =
335334
new net::HttpResponseHeaders(std::string());
336-
orig_response_headers->AddHeader("x-ipfs-path", "/test");
337335
scoped_refptr<net::HttpResponseHeaders> overwrite_response_headers =
338336
new net::HttpResponseHeaders(std::string());
339337
GURL allowed_unsafe_redirect_url;
338+
orig_response_headers->AddHeader("x-ipfs-path", "/test");
340339

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

354-
TEST_F(IPFSRedirectNetworkDelegateHelperTest, HeadersIPFSWorkNoRedirect) {
353+
TEST_F(IPFSRedirectNetworkDelegateHelperTest,
354+
HeadersIPFSWorkNoRedirect_WrongFormat) {
355355
GURL url(
356356
"https://cloudflare-ipfs.com/ipfs/"
357357
"QmSrPmbaUKA3ZodhzPWZnpFgcPMFWF4QsxXbkWfEptTBJd");
@@ -365,7 +365,6 @@ TEST_F(IPFSRedirectNetworkDelegateHelperTest, HeadersIPFSWorkNoRedirect) {
365365

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

components/ipfs/ipfs_utils.cc

+37
Original file line numberDiff line numberDiff line change
@@ -425,4 +425,41 @@ std::string GetRegistryDomainFromIPNS(const GURL& url) {
425425
cid, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
426426
}
427427

428+
absl::optional<GURL> TranslateToCurrentGatewayUrl(const GURL& url) {
429+
if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) {
430+
return absl::nullopt;
431+
}
432+
433+
std::vector<std::string> host_parts = base::SplitStringUsingSubstr(
434+
url.host(), ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
435+
436+
if (host_parts.size() > 2 && IsValidCID(host_parts.at(0)) &&
437+
host_parts.at(1) == "ipfs") {
438+
GURL final_url = GURL("ipfs://" + host_parts.at(0) + url.path());
439+
GURL::Replacements replacements;
440+
replacements.SetQueryStr(url.query_piece());
441+
return final_url.ReplaceComponents(replacements);
442+
}
443+
444+
std::vector<std::string> path_parts = base::SplitStringUsingSubstr(
445+
url.path(), "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
446+
447+
if (path_parts.size() >= 2 && path_parts.at(0) == "ipfs" &&
448+
IsValidCID(path_parts.at(1))) {
449+
std::string final_path;
450+
if (path_parts.size() >= 3) {
451+
std::vector<std::string> final_path_parts(path_parts.begin() + 2,
452+
path_parts.end());
453+
final_path = "/" + base::JoinString(final_path_parts, "/");
454+
}
455+
456+
GURL final_url = GURL("ipfs://" + path_parts.at(1) + final_path);
457+
GURL::Replacements replacements;
458+
replacements.SetQueryStr(url.query_piece());
459+
return final_url.ReplaceComponents(replacements);
460+
}
461+
462+
return absl::nullopt;
463+
}
464+
428465
} // namespace ipfs

components/ipfs/ipfs_utils.h

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <string>
1010

1111
#include "components/version_info/channel.h"
12+
#include "third_party/abseil-cpp/absl/types/optional.h"
1213
#include "url/gurl.h"
1314

1415
class PrefService;
@@ -64,6 +65,7 @@ bool IsIpfsResolveMethodDisabled(PrefService* prefs);
6465
bool IsIpfsResolveMethodAsk(PrefService* prefs);
6566
std::string GetRegistryDomainFromIPNS(const GURL& url);
6667
bool IsValidCIDOrDomain(const std::string& value);
68+
absl::optional<GURL> TranslateToCurrentGatewayUrl(const GURL& url);
6769

6870
} // namespace ipfs
6971

0 commit comments

Comments
 (0)