From fa52670bd30c0eb96811babf3322dd190969f7bd Mon Sep 17 00:00:00 2001 From: oisupov Date: Tue, 7 Mar 2023 02:51:59 +0400 Subject: [PATCH] Allow pinning of gateway-like urls Resolves https://github.com/brave/brave-browser/issues/28169 --- .../browser/brave_wallet_pin_service.cc | 40 ++++- .../brave_wallet_pin_service_unittest.cc | 138 +++++++++++++++++- 2 files changed, 174 insertions(+), 4 deletions(-) diff --git a/components/brave_wallet/browser/brave_wallet_pin_service.cc b/components/brave_wallet/browser/brave_wallet_pin_service.cc index 46592fe80869..782619920c08 100644 --- a/components/brave_wallet/browser/brave_wallet_pin_service.cc +++ b/components/brave_wallet/browser/brave_wallet_pin_service.cc @@ -85,14 +85,37 @@ absl::optional StringToErrorCode( return absl::nullopt; } -absl::optional ExtractCID(const std::string& ipfs_url) { - GURL gurl = GURL(ipfs_url); +absl::optional ExtractIpfsUrl(const std::string& url) { + GURL gurl = GURL(url); + + if (!gurl.is_valid()) { + return absl::nullopt; + } + + if (gurl.SchemeIs(ipfs::kIPFSScheme)) { + return gurl.spec(); + } + + auto scope = ipfs::ExtractSourceFromGateway(gurl); + if (!scope || !scope->SchemeIs(ipfs::kIPFSScheme)) { + return absl::nullopt; + } + + return scope.value().spec(); +} + +absl::optional ExtractCID(const std::string& url) { + GURL gurl = GURL(ExtractIpfsUrl(url).value_or("")); if (!gurl.is_valid()) { return absl::nullopt; } if (!gurl.SchemeIs(ipfs::kIPFSScheme)) { + auto scope = ipfs::ExtractSourceFromGateway(gurl); + if (!scope || !scope->SchemeIs(ipfs::kIPFSScheme)) { + return absl::nullopt; + } return absl::nullopt; } @@ -617,8 +640,19 @@ void BraveWalletPinService::OnTokenMetaDataReceived( cids.push_back(metadata_cid.value()); cids.push_back(image_cid.value()); + auto ipfs_image_url = ExtractIpfsUrl(*image_url); + if (!ipfs_image_url) { + auto pin_error = mojom::PinError::New( + mojom::WalletPinServiceErrorCode::ERR_NON_IPFS_TOKEN_URL, + "Can't find proper image field"); + SetTokenStatus(service, token, + mojom::TokenPinStatusCode::STATUS_PINNING_FAILED, pin_error); + std::move(callback).Run(false, std::move(pin_error)); + return; + } + content_type_checker_->CheckContentTypeSupported( - *image_url, + ipfs_image_url.value(), base::BindOnce(&BraveWalletPinService::OnContentTypeChecked, weak_ptr_factory_.GetWeakPtr(), std::move(service), std::move(token), std::move(cids), std::move(callback))); diff --git a/components/brave_wallet/browser/brave_wallet_pin_service_unittest.cc b/components/brave_wallet/browser/brave_wallet_pin_service_unittest.cc index 96ec7cf02c0e..276f8a243da0 100644 --- a/components/brave_wallet/browser/brave_wallet_pin_service_unittest.cc +++ b/components/brave_wallet/browser/brave_wallet_pin_service_unittest.cc @@ -40,6 +40,10 @@ const char kMonkey3Path[] = "nft.nftstorage.60.0x1.0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d.0x2"; const char kMonkey4Path[] = "nft.local.60.0x1.0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d.0x3"; +const char kMonkey5Path[] = + "nft.local.60.0x1.0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d.0x4"; +const char kMonkey6Path[] = + "nft.local.60.0x1.0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d.0x5"; const char kMonkey1Url[] = "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/2413"; @@ -49,6 +53,10 @@ const char kMonkey3Url[] = "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/2777"; const char kMonkey4Url[] = "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/2888"; +const char kMonkey5Url[] = + "https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/2888"; +const char kMonkey6Url[] = + "https://google.com/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/2888"; const char kMonkey1[] = R"({"image":"ipfs://bafy1", @@ -86,6 +94,24 @@ const char kMonkey4[] = {"trait_type":"Eyes","value":"Closed"}, {"trait_type":"Clothes","value":"Toga"}, {"trait_type":"Hat","value":"Cowboy Hat"}]})"; +const char kMonkey5[] = + R"({"image":"https://ipfs.io/ipfs/bafy3", + "attributes":[ + {"trait_type":"Mouth","value":"Bored Cigarette"}, + {"trait_type":"Fur","value":"Zombie"}, + {"trait_type":"Background","value":"Purple"}, + {"trait_type":"Eyes","value":"Closed"}, + {"trait_type":"Clothes","value":"Toga"}, + {"trait_type":"Hat","value":"Cowboy Hat"}]})"; +const char kMonkey6[] = + R"({"image":"https://google.com/bafy3", + "attributes":[ + {"trait_type":"Mouth","value":"Bored Cigarette"}, + {"trait_type":"Fur","value":"Zombie"}, + {"trait_type":"Background","value":"Purple"}, + {"trait_type":"Eyes","value":"Closed"}, + {"trait_type":"Clothes","value":"Toga"}, + {"trait_type":"Hat","value":"Cowboy Hat"}]})"; base::Time g_overridden_now; std::unique_ptr OverrideWithTimeNow( @@ -299,6 +325,116 @@ TEST_F(BraveWalletPinServiceTest, AddPin) { } } +TEST_F(BraveWalletPinServiceTest, AddPin_GatewayUrl) { + { + ON_CALL(*GetContentTypeChecker(), CheckContentTypeSupported(_, _)) + .WillByDefault(::testing::Invoke( + [](const std::string& cid, + base::OnceCallback)> callback) { + std::move(callback).Run(true); + })); + } + // Right gateway + { + ON_CALL(*GetJsonRpcService(), GetERC721Metadata(_, _, _, _)) + .WillByDefault(::testing::Invoke( + [](const std::string& contract_address, const std::string& token_id, + const std::string& chain_id, + MockJsonRpcService::GetERC721MetadataCallback callback) { + EXPECT_EQ("0x1", chain_id); + EXPECT_EQ("0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", + contract_address); + EXPECT_EQ("0x4", token_id); + std::move(callback).Run(kMonkey5Url, kMonkey5, + mojom::ProviderError::kSuccess, ""); + })); + ON_CALL(*GetIpfsLocalPinService(), AddPins(_, _, _)) + .WillByDefault(::testing::Invoke( + [](const std::string& prefix, const std::vector& cids, + ipfs::AddPinCallback callback) { + EXPECT_EQ(kMonkey5Path, prefix); + EXPECT_EQ("QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq", + cids.at(0)); + EXPECT_EQ("bafy3", cids.at(1)); + std::move(callback).Run(true); + })); + + auto scoped_override = OverrideWithTimeNow(base::Time::FromTimeT(123u)); + + mojom::BlockchainTokenPtr token = + BraveWalletPinService::TokenFromPrefPath(kMonkey5Path); + token->is_erc721 = true; + absl::optional call_status; + service()->AddPin( + std::move(token), absl::nullopt, + base::BindLambdaForTesting( + [&call_status](bool result, mojom::PinErrorPtr error) { + call_status = result; + EXPECT_FALSE(error); + })); + EXPECT_TRUE(call_status.value()); + + const base::Value::Dict* token_record = + GetPrefs() + ->GetDict(kPinnedNFTAssets) + .FindDictByDottedPath(kMonkey5Path); + + base::Value::List expected_cids; + expected_cids.Append("QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq"); + expected_cids.Append("bafy3"); + + EXPECT_EQ(BraveWalletPinService::StatusToString( + mojom::TokenPinStatusCode::STATUS_PINNED), + *(token_record->FindString("status"))); + EXPECT_EQ(nullptr, token_record->FindDict("error")); + EXPECT_EQ(expected_cids, *(token_record->FindList("cids"))); + EXPECT_EQ(base::Time::FromTimeT(123u), + base::ValueToTime(token_record->Find("validate_timestamp"))); + } + + // Wrong gateway + { + ON_CALL(*GetJsonRpcService(), GetERC721Metadata(_, _, _, _)) + .WillByDefault(::testing::Invoke( + [](const std::string& contract_address, const std::string& token_id, + const std::string& chain_id, + MockJsonRpcService::GetERC721MetadataCallback callback) { + EXPECT_EQ("0x1", chain_id); + EXPECT_EQ("0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", + contract_address); + EXPECT_EQ("0x5", token_id); + std::move(callback).Run(kMonkey6Url, kMonkey6, + mojom::ProviderError::kSuccess, ""); + })); + + mojom::BlockchainTokenPtr token = + BraveWalletPinService::TokenFromPrefPath(kMonkey6Path); + token->is_erc721 = true; + absl::optional call_status; + service()->AddPin( + std::move(token), absl::nullopt, + base::BindLambdaForTesting( + [&call_status](bool result, mojom::PinErrorPtr error) { + call_status = result; + EXPECT_TRUE(error); + })); + + EXPECT_FALSE(call_status.value()); + + const base::Value::Dict* token_record = + GetPrefs() + ->GetDict(kPinnedNFTAssets) + .FindDictByDottedPath(kMonkey6Path); + + EXPECT_EQ(BraveWalletPinService::StatusToString( + mojom::TokenPinStatusCode::STATUS_PINNING_FAILED), + *(token_record->FindString("status"))); + EXPECT_EQ(BraveWalletPinService::ErrorCodeToString( + mojom::WalletPinServiceErrorCode::ERR_NON_IPFS_TOKEN_URL), + token_record->FindByDottedPath("error.error_code")->GetString()); + } +} + TEST_F(BraveWalletPinServiceTest, AddPin_ContentVerification) { { ON_CALL(*GetContentTypeChecker(), CheckContentTypeSupported(_, _)) @@ -471,7 +607,7 @@ TEST_F(BraveWalletPinServiceTest, AddPin_ContentVerification) { } } -TEST_F(BraveWalletPinServiceTest, AddPin_NonIpfsImage) { +TEST_F(BraveWalletPinServiceTest, _NonIpfsImage) { { ON_CALL(*GetJsonRpcService(), GetERC721Metadata(_, _, _, _)) .WillByDefault(::testing::Invoke(