Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify NFT content media type before pinning #17392

Merged
merged 2 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion browser/brave_wallet/brave_wallet_pin_service_factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "chrome/browser/profiles/incognito_helpers.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/storage_partition.h"

namespace brave_wallet {

Expand Down Expand Up @@ -86,7 +87,11 @@ KeyedService* BraveWalletPinServiceFactory::BuildServiceInstanceFor(
user_prefs::UserPrefs::Get(context),
JsonRpcServiceFactory::GetServiceForContext(context),
ipfs::IpfsLocalPinServiceFactory::GetServiceForContext(context),
ipfs::IpfsServiceFactory::GetForContext(context));
ipfs::IpfsServiceFactory::GetForContext(context),
std::make_unique<ContentTypeChecker>(
user_prefs::UserPrefs::Get(context),
context->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess()));
}

content::BrowserContext* BraveWalletPinServiceFactory::GetBrowserContextToUse(
Expand Down
165 changes: 151 additions & 14 deletions components/brave_wallet/browser/brave_wallet_pin_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "brave/components/ipfs/ipfs_constants.h"
#include "brave/components/ipfs/ipfs_utils.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "services/network/public/cpp/resource_request.h"

namespace brave_wallet {

Expand Down Expand Up @@ -78,13 +79,19 @@ absl::optional<mojom::WalletPinServiceErrorCode> StringToErrorCode(
return mojom::WalletPinServiceErrorCode::ERR_NOT_PINNED;
} else if (error == "ERR_PINNING_FAILED") {
return mojom::WalletPinServiceErrorCode::ERR_PINNING_FAILED;
} else if (error == "ERR_MEDIA_TYPE_UNSUPPORTED") {
return mojom::WalletPinServiceErrorCode::ERR_MEDIA_TYPE_UNSUPPORTED;
}
return absl::nullopt;
}

absl::optional<std::string> ExtractCID(const std::string& ipfs_url) {
GURL gurl = GURL(ipfs_url);

if (!gurl.is_valid()) {
return absl::nullopt;
}

if (!gurl.SchemeIs(ipfs::kIPFSScheme)) {
return absl::nullopt;
}
Expand Down Expand Up @@ -149,11 +156,97 @@ std::string BraveWalletPinService::ErrorCodeToString(
return "ERR_NOT_PINNED";
case mojom::WalletPinServiceErrorCode::ERR_PINNING_FAILED:
return "ERR_PINNING_FAILED";
case mojom::WalletPinServiceErrorCode::ERR_MEDIA_TYPE_UNSUPPORTED:
return "ERR_MEDIA_TYPE_UNSUPPORTED";
}
NOTREACHED();
return "";
}

ContentTypeChecker::ContentTypeChecker(
PrefService* pref_service,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: pref_service_(pref_service),
url_loader_factory_(std::move(url_loader_factory)) {}

ContentTypeChecker::ContentTypeChecker() = default;

ContentTypeChecker::~ContentTypeChecker() = default;

void ContentTypeChecker::CheckContentTypeSupported(
const std::string& ipfs_url,
base::OnceCallback<void(absl::optional<bool>)> callback) {
// Create a request with no data or cookies.
auto resource_request = std::make_unique<network::ResourceRequest>();
GURL translated_url;
if (!ipfs::TranslateIPFSURI(GURL(ipfs_url), &translated_url,
ipfs::GetDefaultNFTIPFSGateway(pref_service_),
false)) {
std::move(callback).Run(false);
return;
}
resource_request->url = translated_url;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->redirect_mode = ::network::mojom::RedirectMode::kFollow;

auto annotation =
net::DefineNetworkTrafficAnnotation("brave_wallet_pin_service", R"(
semantics {
sender: "Brave wallet pin service"
description:
"This service is used to pin NFTs"
"which are added to the wallet."
trigger:
"Triggered by enable auto-pinning mode"
"from the Brave Wallet page or settings."
data:
"Options of the commands."
destination: WEBSITE
}
policy {
cookies_allowed: NO
setting:
"You can enable or disable this feature in brave://settings."
policy_exception_justification:
"Not implemented."
}
)");

auto url_loader =
network::SimpleURLLoader::Create(std::move(resource_request), annotation);
auto* url_loader_ptr = url_loader.get();
auto it = loaders_in_progress_.insert(loaders_in_progress_.end(),
std::move(url_loader));

url_loader_ptr->SetTimeoutDuration(base::Seconds(60));
url_loader_ptr->SetRetryOptions(
5, network::SimpleURLLoader::RetryMode::RETRY_ON_5XX |
network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE);
url_loader_ptr->DownloadHeadersOnly(
url_loader_factory_.get(),
base::BindOnce(&ContentTypeChecker::OnHeadersFetched,
weak_ptr_factory_.GetWeakPtr(), std::move(it),
std::move(callback)));
}

void ContentTypeChecker::OnHeadersFetched(
UrlLoaderList::iterator loader_it,
base::OnceCallback<void(absl::optional<bool>)> callback,
scoped_refptr<net::HttpResponseHeaders> headers) {
loaders_in_progress_.erase(loader_it);
if (!headers) {
std::move(callback).Run(absl::nullopt);
return;
}
std::string content_type_value;
headers->GetMimeType(&content_type_value);
if (base::StartsWith(content_type_value, "image/")) {
std::move(callback).Run(true);
} else {
std::move(callback).Run(false);
}
}

// static
bool BraveWalletPinService::IsTokenSupportedForPinning(
const mojom::BlockchainTokenPtr& token) {
Expand Down Expand Up @@ -204,11 +297,13 @@ BraveWalletPinService::BraveWalletPinService(
PrefService* prefs,
JsonRpcService* service,
ipfs::IpfsLocalPinService* local_pin_service,
IpfsService* ipfs_service)
IpfsService* ipfs_service,
std::unique_ptr<ContentTypeChecker> content_type_checker)
: prefs_(prefs),
json_rpc_service_(service),
local_pin_service_(local_pin_service),
ipfs_service_(ipfs_service) {
ipfs_service_(ipfs_service),
content_type_checker_(std::move(content_type_checker)) {
ipfs_service_->AddObserver(this);
}

Expand Down Expand Up @@ -480,8 +575,8 @@ void BraveWalletPinService::OnTokenMetaDataReceived(
return;
}

GURL token_gurl = GURL(token_url);
if (!token_gurl.SchemeIs(ipfs::kIPFSScheme)) {
auto metadata_cid = ExtractCID(token_url);
if (!metadata_cid) {
auto pin_error = mojom::PinError::New(
mojom::WalletPinServiceErrorCode::ERR_NON_IPFS_TOKEN_URL,
"Metadata has non-ipfs url");
Expand All @@ -504,23 +599,65 @@ void BraveWalletPinService::OnTokenMetaDataReceived(
return;
}

auto* image_url = parsed_result->FindStringKey("image");
auto image_cid =
image_url != nullptr ? ExtractCID(*image_url) : absl::nullopt;

if (!image_cid) {
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;
}

std::vector<std::string> cids;
cids.push_back(metadata_cid.value());
cids.push_back(image_cid.value());

cids.push_back(ExtractCID(token_url).value());
auto* image = parsed_result->FindStringKey("image");
if (image) {
auto image_cid = ExtractCID(*image);
if (image_cid) {
cids.push_back(image_cid.value());
}
content_type_checker_->CheckContentTypeSupported(
*image_url,
base::BindOnce(&BraveWalletPinService::OnContentTypeChecked,
weak_ptr_factory_.GetWeakPtr(), std::move(service),
std::move(token), std::move(cids), std::move(callback)));
}

void BraveWalletPinService::OnContentTypeChecked(
absl::optional<std::string> service,
mojom::BlockchainTokenPtr token,
std::vector<std::string> cids,
AddPinCallback callback,
absl::optional<bool> result) {
if (!result.has_value()) {
SetTokenStatus(service, token,
mojom::TokenPinStatusCode::STATUS_PINNING_FAILED, nullptr);
std::move(callback).Run(
false, mojom::PinError::New(
mojom::WalletPinServiceErrorCode::ERR_FETCH_METADATA_FAILED,
"Failed to verify media type"));
return;
}

if (!result.value()) {
SetTokenStatus(service, token,
mojom::TokenPinStatusCode::STATUS_PINNING_FAILED, nullptr);
std::move(callback).Run(
false, mojom::PinError::New(
mojom::WalletPinServiceErrorCode::ERR_MEDIA_TYPE_UNSUPPORTED,
"Media type not supported"));
return;
}

auto path = GetTokenPrefPath(service, token);
if (!path) {
SetTokenStatus(service, token,
mojom::TokenPinStatusCode::STATUS_PINNING_FAILED, nullptr);
std::move(callback).Run(
false,
mojom::PinError::New(mojom::WalletPinServiceErrorCode::ERR_WRONG_TOKEN,
"Wrong token data"));
false, mojom::PinError::New(
mojom::WalletPinServiceErrorCode::ERR_WRONG_METADATA_FORMAT,
"Wrong token data"));
return;
}

Expand Down
50 changes: 46 additions & 4 deletions components/brave_wallet/browser/brave_wallet_pin_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_BRAVE_WALLET_PIN_SERVICE_H_
#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_BRAVE_WALLET_PIN_SERVICE_H_

#include <list>
#include <memory>
#include <set>
#include <string>
#include <vector>
Expand All @@ -23,10 +25,42 @@
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace brave_wallet {

class ContentTypeChecker {
public:
ContentTypeChecker(
PrefService* pref_service,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
virtual ~ContentTypeChecker();

virtual void CheckContentTypeSupported(
const std::string& cid,
base::OnceCallback<void(absl::optional<bool>)> callback);

protected:
// For tests
ContentTypeChecker();

private:
using UrlLoaderList = std::list<std::unique_ptr<network::SimpleURLLoader>>;

void OnHeadersFetched(UrlLoaderList::iterator iterator,
base::OnceCallback<void(absl::optional<bool>)> callback,
scoped_refptr<net::HttpResponseHeaders> headers);

raw_ptr<PrefService> pref_service_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
// List of requests are actively being sent.
UrlLoaderList loaders_in_progress_;

base::WeakPtrFactory<ContentTypeChecker> weak_ptr_factory_{this};
};

/**
* At the moment only local pinning is supported so use absl::nullopt
* for optional service argument.
Expand All @@ -35,10 +69,12 @@ class BraveWalletPinService : public KeyedService,
public brave_wallet::mojom::WalletPinService,
public ipfs::IpfsServiceObserver {
public:
BraveWalletPinService(PrefService* prefs,
JsonRpcService* service,
ipfs::IpfsLocalPinService* local_pin_service,
IpfsService* ipfs_service);
BraveWalletPinService(
PrefService* prefs,
JsonRpcService* service,
ipfs::IpfsLocalPinService* local_pin_service,
IpfsService* ipfs_service,
std::unique_ptr<ContentTypeChecker> content_type_checker);
~BraveWalletPinService() override;

virtual void Restore();
Expand Down Expand Up @@ -134,6 +170,11 @@ class BraveWalletPinService : public KeyedService,
const std::string& result,
mojom::ProviderError error,
const std::string& error_message);
void OnContentTypeChecked(absl::optional<std::string> service,
mojom::BlockchainTokenPtr token,
std::vector<std::string> cids,
AddPinCallback callback,
absl::optional<bool> result);

// ipfs::IpfsServiceObserver
void OnIpfsLaunched(bool result, int64_t pid) override;
Expand All @@ -149,6 +190,7 @@ class BraveWalletPinService : public KeyedService,
raw_ptr<JsonRpcService> json_rpc_service_ = nullptr;
raw_ptr<ipfs::IpfsLocalPinService> local_pin_service_ = nullptr;
raw_ptr<IpfsService> ipfs_service_ = nullptr;
std::unique_ptr<ContentTypeChecker> content_type_checker_;

base::WeakPtrFactory<BraveWalletPinService> weak_ptr_factory_{this};
};
Expand Down
Loading