blob: 87b33d0f5afda7bda73af1a283c5f3544537af3a [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/payments/content/service_worker_payment_app_finder.h"
#include <algorithm>
#include <vector>
#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/check.h"
#include "base/memory/ref_counted.h"
#include "base/stl_util.h"
#include "base/supports_user_data.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/payments/content/developer_console_logger.h"
#include "components/payments/content/installable_payment_app_crawler.h"
#include "components/payments/content/manifest_verifier.h"
#include "components/payments/content/payment_manifest_web_data_service.h"
#include "components/payments/content/utility/payment_manifest_parser.h"
#include "components/payments/core/features.h"
#include "components/payments/core/method_strings.h"
#include "components/payments/core/payment_manifest_downloader.h"
#include "components/payments/core/url_util.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/payment_app_provider.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/stored_payment_app.h"
#include "content/public/browser/web_contents.h"
#include "net/url_request/url_request_context_getter.h"
#include "ui/gfx/image/image.h"
#include "url/url_canon.h"
namespace payments {
namespace {
// Returns true if |requested| is empty or contains at least one of the items in
// |capabilities|.
template <typename T>
bool CapabilityMatches(const std::vector<T>& requested,
const std::vector<int32_t>& capabilities) {
if (requested.empty())
return true;
for (const auto& request : requested) {
if (base::Contains(capabilities, static_cast<int32_t>(request)))
return true;
}
return false;
}
// Returns true if the basic-card |capabilities| of the payment app match the
// |request|.
bool BasicCardCapabilitiesMatch(
const std::vector<content::StoredCapabilities>& capabilities,
const mojom::PaymentMethodDataPtr& request) {
for (const auto& capability : capabilities) {
if (CapabilityMatches(request->supported_networks,
capability.supported_card_networks)) {
return true;
}
}
return capabilities.empty() && request->supported_networks.empty();
}
// Returns true if |app| supports at least one of the |requests|.
bool AppSupportsAtLeastOneRequestedMethodData(
const content::StoredPaymentApp& app,
const std::vector<mojom::PaymentMethodDataPtr>& requests) {
for (const auto& enabled_method : app.enabled_methods) {
for (const auto& request : requests) {
if (enabled_method == request->supported_method) {
if (enabled_method != methods::kBasicCard ||
BasicCardCapabilitiesMatch(app.capabilities, request)) {
return true;
}
}
}
}
return false;
}
void RemovePortNumbersFromScopesForTest(
content::PaymentAppProvider::PaymentApps* apps) {
GURL::Replacements replacements;
replacements.ClearPort();
for (auto& app : *apps) {
app.second->scope = app.second->scope.ReplaceComponents(replacements);
}
}
class SelfDeletingServiceWorkerPaymentAppFinder
: public base::SupportsUserData::Data {
public:
static base::WeakPtr<SelfDeletingServiceWorkerPaymentAppFinder>
CreateAndSetOwnedBy(base::SupportsUserData* owner) {
auto owned =
std::make_unique<SelfDeletingServiceWorkerPaymentAppFinder>(owner);
auto* pointer = owned.get();
owner->SetUserData(pointer, std::move(owned));
return pointer->weak_ptr_factory_.GetWeakPtr();
}
explicit SelfDeletingServiceWorkerPaymentAppFinder(
base::SupportsUserData* owner)
: owner_(owner) {}
SelfDeletingServiceWorkerPaymentAppFinder(
const SelfDeletingServiceWorkerPaymentAppFinder& other) = delete;
SelfDeletingServiceWorkerPaymentAppFinder& operator=(
const SelfDeletingServiceWorkerPaymentAppFinder& other) = delete;
~SelfDeletingServiceWorkerPaymentAppFinder() override = default;
// After |callback| has fired, the factory refreshes its own cache in the
// background. Once the cache has been refreshed, the factory invokes the
// |finished_using_resources_callback|. At this point, it's safe to delete
// this factory. Don't destroy the factory and don't call this method again
// until |finished_using_resources_callback| has run.
void GetAllPaymentApps(
const url::Origin& merchant_origin,
content::RenderFrameHost* initiator_render_frame_host,
std::unique_ptr<PaymentManifestDownloader> downloader,
scoped_refptr<PaymentManifestWebDataService> cache,
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
bool may_crawl_for_installable_payment_apps,
ServiceWorkerPaymentAppFinder::GetAllPaymentAppsCallback callback,
base::OnceClosure finished_using_resources_callback) {
DCHECK(!verifier_);
downloader_ = std::move(downloader);
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(initiator_render_frame_host);
parser_ = std::make_unique<PaymentManifestParser>(
std::make_unique<DeveloperConsoleLogger>(web_contents));
cache_ = cache;
verifier_ = std::make_unique<ManifestVerifier>(
merchant_origin, web_contents, downloader_.get(), parser_.get(),
cache_.get());
if (may_crawl_for_installable_payment_apps &&
base::FeatureList::IsEnabled(
features::kWebPaymentsJustInTimePaymentApp)) {
// Construct crawler in constructor to allow it observe the web_contents.
crawler_ = std::make_unique<InstallablePaymentAppCrawler>(
merchant_origin, initiator_render_frame_host, web_contents,
downloader_.get(), parser_.get(), cache_.get());
if (ignore_port_in_origin_comparison_for_testing_)
crawler_->IgnorePortInOriginComparisonForTesting();
}
// Method data cannot be copied and is passed in as a const-ref, which
// cannot be moved, so make a manual copy for using below.
for (const auto& method_data : requested_method_data) {
requested_method_data_.emplace_back(method_data.Clone());
}
callback_ = std::move(callback);
finished_using_resources_callback_ =
std::move(finished_using_resources_callback);
content::PaymentAppProvider::GetInstance()->GetAllPaymentApps(
web_contents->GetBrowserContext(),
base::BindOnce(
&SelfDeletingServiceWorkerPaymentAppFinder::OnGotAllPaymentApps,
weak_ptr_factory_.GetWeakPtr()));
}
void IgnorePortInOriginComparisonForTesting() {
ignore_port_in_origin_comparison_for_testing_ = true;
}
private:
// base::SupportsUserData::Data implementation.
std::unique_ptr<Data> Clone() override {
return nullptr; // Cloning is not supported.
}
static void RemoveUnrequestedMethods(
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
content::PaymentAppProvider::PaymentApps* apps) {
std::set<std::string> requested_methods;
for (const auto& requested_method_datum : requested_method_data) {
requested_methods.insert(requested_method_datum->supported_method);
}
for (auto& app : *apps) {
std::sort(app.second->enabled_methods.begin(),
app.second->enabled_methods.end());
app.second->enabled_methods =
base::STLSetIntersection<std::vector<std::string>>(
app.second->enabled_methods, requested_methods);
}
}
void OnGotAllPaymentApps(content::PaymentAppProvider::PaymentApps apps) {
if (ignore_port_in_origin_comparison_for_testing_)
RemovePortNumbersFromScopesForTest(&apps);
RemoveUnrequestedMethods(requested_method_data_, &apps);
ServiceWorkerPaymentAppFinder::RemoveAppsWithoutMatchingMethodData(
requested_method_data_, &apps);
if (apps.empty()) {
OnPaymentAppsVerified(std::move(apps), first_error_message_);
OnPaymentAppsVerifierFinishedUsingResources();
return;
}
// The |verifier_| will invoke |OnPaymentAppsVerified| with the list of all
// valid payment apps. This list may be empty, if none of the apps were
// found to be valid.
is_payment_verifier_finished_using_resources_ = false;
verifier_->Verify(
std::move(apps),
base::BindOnce(
&SelfDeletingServiceWorkerPaymentAppFinder::OnPaymentAppsVerified,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&SelfDeletingServiceWorkerPaymentAppFinder::
OnPaymentAppsVerifierFinishedUsingResources,
weak_ptr_factory_.GetWeakPtr()));
}
void OnPaymentAppsVerified(content::PaymentAppProvider::PaymentApps apps,
const std::string& error_message) {
if (first_error_message_.empty())
first_error_message_ = error_message;
std::set<GURL> method_manifest_urls_for_icon_refetch;
installed_apps_ = std::move(apps);
for (auto& app : installed_apps_) {
if (app.second->icon.get() && !app.second->icon.get()->drawsNothing()) {
continue;
}
for (const auto& method : app.second->enabled_methods) {
// Only payment methods with manifests are eligible for refetching the
// icon of their installed payment apps.
GURL method_manifest_url = GURL(method);
if (!UrlUtil::IsValidUrlBasedPaymentMethodIdentifier(
method_manifest_url)) {
continue;
}
method_manifest_urls_for_icon_refetch.insert(method_manifest_url);
}
}
if ((installed_apps_.empty() ||
!method_manifest_urls_for_icon_refetch.empty()) &&
crawler_ != nullptr) {
// Crawls installable web payment apps if no web payment apps have been
// installed or when an installed app is missing an icon.
is_payment_app_crawler_finished_using_resources_ = false;
crawler_->Start(
requested_method_data_,
std::move(method_manifest_urls_for_icon_refetch),
base::BindOnce(
&SelfDeletingServiceWorkerPaymentAppFinder::OnPaymentAppsCrawled,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&SelfDeletingServiceWorkerPaymentAppFinder::
OnPaymentAppsCrawlerFinishedUsingResources,
weak_ptr_factory_.GetWeakPtr()));
return;
}
// Release crawler_ since it will not be used from now on.
crawler_.reset();
std::move(callback_).Run(
std::move(installed_apps_),
ServiceWorkerPaymentAppFinder::InstallablePaymentApps(),
first_error_message_);
}
void OnPaymentAppsCrawled(
std::map<GURL, std::unique_ptr<WebAppInstallationInfo>> apps_info,
std::map<GURL, std::unique_ptr<RefetchedIcon>> refetched_icons,
const std::string& error_message) {
if (first_error_message_.empty())
first_error_message_ = error_message;
for (auto& refetched_icon : refetched_icons) {
GURL web_app_manifest_url = refetched_icon.first;
RefetchedIcon* data = refetched_icon.second.get();
for (auto& app : installed_apps_) {
// It is possible (unlikely) to have multiple apps with same origins.
// The proper validation is to store web_app_manifest_url in
// StoredPaymentApp and confirm that it is the same as the
// web_app_manifest_url from which icon is fetched.
if (crawler_->IsSameOriginWith(GURL(app.second->scope),
web_app_manifest_url)) {
UpdatePaymentAppIcon(app.second, data->icon, data->method_name);
app.second->icon = std::move(data->icon);
break;
}
}
}
std::move(callback_).Run(std::move(installed_apps_), std::move(apps_info),
first_error_message_);
}
void UpdatePaymentAppIcon(
const std::unique_ptr<content::StoredPaymentApp>& app,
const std::unique_ptr<SkBitmap>& icon,
const std::string& method_name) {
number_of_app_icons_to_update_++;
DCHECK(!icon->empty());
std::string string_encoded_icon;
gfx::Image decoded_image = gfx::Image::CreateFrom1xBitmap(*(icon));
scoped_refptr<base::RefCountedMemory> raw_data =
decoded_image.As1xPNGBytes();
base::Base64Encode(
base::StringPiece(raw_data->front_as<char>(), raw_data->size()),
&string_encoded_icon);
auto* browser_context =
static_cast<content::WebContents*>(owner_)->GetBrowserContext();
content::PaymentAppProvider::GetInstance()->UpdatePaymentAppIcon(
browser_context, app->registration_id, app->scope.spec(), app->name,
string_encoded_icon, method_name, app->supported_delegations,
base::BindOnce(
&SelfDeletingServiceWorkerPaymentAppFinder::OnUpdatePaymentAppIcon,
weak_ptr_factory_.GetWeakPtr()));
}
void OnUpdatePaymentAppIcon(payments::mojom::PaymentHandlerStatus status) {
DCHECK(number_of_app_icons_to_update_ > 0);
number_of_app_icons_to_update_--;
if (number_of_app_icons_to_update_ == 0)
FinishUsingResourcesIfReady();
}
void OnPaymentAppsCrawlerFinishedUsingResources() {
crawler_.reset();
is_payment_app_crawler_finished_using_resources_ = true;
FinishUsingResourcesIfReady();
}
void OnPaymentAppsVerifierFinishedUsingResources() {
verifier_.reset();
is_payment_verifier_finished_using_resources_ = true;
FinishUsingResourcesIfReady();
}
void FinishUsingResourcesIfReady() {
if (is_payment_verifier_finished_using_resources_ &&
is_payment_app_crawler_finished_using_resources_ &&
!finished_using_resources_callback_.is_null() &&
number_of_app_icons_to_update_ == 0) {
downloader_.reset();
parser_.reset();
std::move(finished_using_resources_callback_).Run();
base::ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
FROM_HERE,
base::BindOnce(&SelfDeletingServiceWorkerPaymentAppFinder::DeleteSelf,
weak_ptr_factory_.GetWeakPtr()));
}
}
void DeleteSelf() { owner_->RemoveUserData(this); }
// |owner_| owns this SelfDeletingServiceWorkerPaymentAppFinder, so it is
// always valid.
base::SupportsUserData* owner_;
std::unique_ptr<PaymentManifestDownloader> downloader_;
std::unique_ptr<PaymentManifestParser> parser_;
scoped_refptr<PaymentManifestWebDataService> cache_;
std::vector<mojom::PaymentMethodDataPtr> requested_method_data_;
ServiceWorkerPaymentAppFinder::GetAllPaymentAppsCallback callback_;
base::OnceClosure finished_using_resources_callback_;
std::string first_error_message_;
std::unique_ptr<ManifestVerifier> verifier_;
bool is_payment_verifier_finished_using_resources_ = true;
std::unique_ptr<InstallablePaymentAppCrawler> crawler_;
bool is_payment_app_crawler_finished_using_resources_ = true;
bool ignore_port_in_origin_comparison_for_testing_ = false;
content::PaymentAppProvider::PaymentApps installed_apps_;
size_t number_of_app_icons_to_update_ = 0;
base::WeakPtrFactory<SelfDeletingServiceWorkerPaymentAppFinder>
weak_ptr_factory_{this};
};
} // namespace
ServiceWorkerPaymentAppFinder::~ServiceWorkerPaymentAppFinder() = default;
void ServiceWorkerPaymentAppFinder::GetAllPaymentApps(
const url::Origin& merchant_origin,
scoped_refptr<PaymentManifestWebDataService> cache,
std::vector<mojom::PaymentMethodDataPtr> requested_method_data,
bool may_crawl_for_installable_payment_apps,
GetAllPaymentAppsCallback callback,
base::OnceClosure finished_writing_cache_callback_for_testing) {
DCHECK(!requested_method_data.empty());
// Do not look up payment handlers for ignored payment methods.
base::EraseIf(requested_method_data,
[&](const mojom::PaymentMethodDataPtr& method_data) {
return base::Contains(ignored_methods_,
method_data->supported_method);
});
if (requested_method_data.empty()) {
std::move(callback).Run(
content::PaymentAppProvider::PaymentApps(),
std::map<GURL, std::unique_ptr<WebAppInstallationInfo>>(),
/*error_message=*/"");
return;
}
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(rfh_);
auto self_delete_factory =
SelfDeletingServiceWorkerPaymentAppFinder::CreateAndSetOwnedBy(
web_contents);
std::unique_ptr<PaymentManifestDownloader> downloader;
if (test_downloader_ != nullptr) {
downloader = std::move(test_downloader_);
self_delete_factory->IgnorePortInOriginComparisonForTesting();
} else {
downloader = std::make_unique<payments::PaymentManifestDownloader>(
std::make_unique<DeveloperConsoleLogger>(web_contents),
content::BrowserContext::GetDefaultStoragePartition(
web_contents->GetBrowserContext())
->GetURLLoaderFactoryForBrowserProcess());
}
self_delete_factory->GetAllPaymentApps(
merchant_origin, rfh_, std::move(downloader), cache,
requested_method_data, may_crawl_for_installable_payment_apps,
std::move(callback),
std::move(finished_writing_cache_callback_for_testing));
}
// static
void ServiceWorkerPaymentAppFinder::RemoveAppsWithoutMatchingMethodData(
const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
content::PaymentAppProvider::PaymentApps* apps) {
for (auto it = apps->begin(); it != apps->end();) {
if (AppSupportsAtLeastOneRequestedMethodData(*it->second,
requested_method_data)) {
++it;
} else {
it = apps->erase(it);
}
}
}
void ServiceWorkerPaymentAppFinder::IgnorePaymentMethodForTest(
const std::string& method) {
ignored_methods_.insert(method);
}
ServiceWorkerPaymentAppFinder::ServiceWorkerPaymentAppFinder(
content::RenderFrameHost* rfh)
: rfh_(rfh),
ignored_methods_({methods::kGooglePlayBilling}),
test_downloader_(nullptr) {}
void ServiceWorkerPaymentAppFinder::
SetDownloaderAndIgnorePortInOriginComparisonForTesting(
std::unique_ptr<PaymentManifestDownloader> downloader) {
test_downloader_ = std::move(downloader);
}
RENDER_DOCUMENT_HOST_USER_DATA_KEY_IMPL(ServiceWorkerPaymentAppFinder)
} // namespace payments