Commit 4ab8845f authored by Rouslan Solomakhin's avatar Rouslan Solomakhin Committed by Commit Bot

[Web Payment] Hosting manifest at payment method identifier URL.

Before this patch, the payment method manifest had to be hosted at a
location that is pointed to from the payment method identifier URL via
the HTTP Link rel=payment-method-manifest header. This complicated
testing in Chrome and prevented hosting payment method manifests on
popular hosting services, such as GitHub pages.

This patch falls back to returning the content of the page if it does
not have HTTP Link rel=payment-method-manifest header or that header is
not valid. The downloader uses HTTP GET instead of HTTP HEAD for the
payment method manifest, since the response body has to be considered.
An empty response body now generates an error earlier: in the downloader
instead of parser, because the parser always rejected empty input.

After this patch, it's possible to host the payment method manifest
directly at the URL of the payment method identifier, which makes
testing Chrome and developing payment apps easier.

Bug: 1035147
Change-Id: Id07c9023bac222ed45eecb8df91108f4df51e356
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2113735Reviewed-by: default avatarDanyao Wang <danyao@chromium.org>
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#753569}
parent faf7d2bb
...@@ -191,9 +191,8 @@ public class PaymentManifestDownloaderTest implements ManifestDownloadCallback { ...@@ -191,9 +191,8 @@ public class PaymentManifestDownloaderTest implements ManifestDownloadCallback {
Assert.assertTrue( Assert.assertTrue(
"Payment method manifest should have not have been downloaded.", mDownloadFailure); "Payment method manifest should have not have been downloaded.", mDownloadFailure);
Assert.assertEquals("Unable to make a HEAD request to \"" + uri.toString() Assert.assertEquals(
+ "\" for payment method manifest.", "Unable to download payment manifest \"" + uri.toString() + "\".", mErrorMessage);
mErrorMessage);
} }
@Test @Test
......
...@@ -533,8 +533,8 @@ IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, ThreeTypesOfMethods) { ...@@ -533,8 +533,8 @@ IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, ThreeTypesOfMethods) {
IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
SinglePaymentMethodName404) { SinglePaymentMethodName404) {
std::string expected_pattern = std::string expected_pattern =
"Unable to make a HEAD request to " "Unable to download payment manifest "
"\"https://127.0.0.1:\\d+/404.test/webpay\" for payment method manifest."; "\"https://127.0.0.1:\\d+/404.test/webpay\".";
{ {
content::PaymentAppProvider::PaymentApps apps; content::PaymentAppProvider::PaymentApps apps;
apps[0] = std::make_unique<content::StoredPaymentApp>(); apps[0] = std::make_unique<content::StoredPaymentApp>();
...@@ -572,9 +572,8 @@ IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, ...@@ -572,9 +572,8 @@ IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest, IN_PROC_BROWSER_TEST_F(ManifestVerifierBrowserTest,
MultiplePaymentMethodName404) { MultiplePaymentMethodName404) {
std::string expected_pattern = std::string expected_pattern =
"Unable to make a HEAD request to " "Unable to download payment manifest "
"\"https://127.0.0.1:\\d+/404(aswell)?.test/webpay\" for payment method " "\"https://127.0.0.1:\\d+/404(aswell)?.test/webpay\".";
"manifest.";
{ {
content::PaymentAppProvider::PaymentApps apps; content::PaymentAppProvider::PaymentApps apps;
apps[0] = std::make_unique<content::StoredPaymentApp>(); apps[0] = std::make_unique<content::StoredPaymentApp>();
......
...@@ -53,9 +53,6 @@ const char kCrossOriginWebAppManifestNotAllowed[] = ...@@ -53,9 +53,6 @@ const char kCrossOriginWebAppManifestNotAllowed[] =
const char kDetailedInvalidSslCertificateMessageFormat[] = const char kDetailedInvalidSslCertificateMessageFormat[] =
"SSL certificate is not valid. Security level: $."; "SSL certificate is not valid. Security level: $.";
const char kHttpHeadRequestFailed[] =
"Unable to make a HEAD request to \"$1\" for payment method manifest.";
const char kHttpStatusCodeNotAllowed[] = const char kHttpStatusCodeNotAllowed[] =
"HTTP status code $1 \"$2\" not allowed for payment method manifest " "HTTP status code $1 \"$2\" not allowed for payment method manifest "
"\"$3\"."; "\"$3\".";
...@@ -89,9 +86,6 @@ const char kMissingMethodNameFromPaymentApp[] = ...@@ -89,9 +86,6 @@ const char kMissingMethodNameFromPaymentApp[] =
const char kMultiplePaymentMethodsNotSupportedFormat[] = const char kMultiplePaymentMethodsNotSupportedFormat[] =
"The payment methods $ are not supported."; "The payment methods $ are not supported.";
const char kNoLinkRelPaymentMethodManifestHttpHeader[] =
"No \"Link: rel=payment-method-manifest\" HTTP header found at \"$1\".";
const char kNoResponseToPaymentEvent[] = const char kNoResponseToPaymentEvent[] =
"Payment handler did not respond to \"paymentrequest\" event."; "Payment handler did not respond to \"paymentrequest\" event.";
...@@ -220,5 +214,12 @@ const char kCanMakePaymentEventNoExplicitlyVerifiedMethods[] = ...@@ -220,5 +214,12 @@ const char kCanMakePaymentEventNoExplicitlyVerifiedMethods[] =
const char kGenericPaymentMethodNotSupportedMessage[] = const char kGenericPaymentMethodNotSupportedMessage[] =
"Payment method not supported."; "Payment method not supported.";
const char kNoContentAndNoLinkHeader[] =
"No content and no \"Link: rel=payment-method-manifest\" HTTP header found "
"at \"$1\".";
const char kNoContentInPaymentManifest[] =
"No content found in payment manifest \"$1\".";
} // namespace errors } // namespace errors
} // namespace payments } // namespace payments
...@@ -71,10 +71,6 @@ extern const char kCrossOriginWebAppManifestNotAllowed[]; ...@@ -71,10 +71,6 @@ extern const char kCrossOriginWebAppManifestNotAllowed[];
// to replace. // to replace.
extern const char kDetailedInvalidSslCertificateMessageFormat[]; extern const char kDetailedInvalidSslCertificateMessageFormat[];
// Used when a HEAD request for URL A fails. This format should be used with
// base::ReplaceStringPlaceholders(fmt, {A}, nullptr).
extern const char kHttpHeadRequestFailed[];
// Used for HTTP redirects that are prohibited for payment method manifests. // Used for HTTP redirects that are prohibited for payment method manifests.
// This format should be used with base::ReplaceStringPlaceholders(fmt, // This format should be used with base::ReplaceStringPlaceholders(fmt,
// {http_code, http_code_phrase, original_url}, nullptr). // {http_code, http_code_phrase, original_url}, nullptr).
...@@ -117,11 +113,6 @@ extern const char kMissingMethodNameFromPaymentApp[]; ...@@ -117,11 +113,6 @@ extern const char kMissingMethodNameFromPaymentApp[];
// where "$" is the character to replace. // where "$" is the character to replace.
extern const char kMultiplePaymentMethodsNotSupportedFormat[]; extern const char kMultiplePaymentMethodsNotSupportedFormat[];
// Used when the payment method URL A does not have a "Link:
// rel=payment-method-manifest" HTTP header. This format should be used with
// base::ReplaceStringPlaceholders(fmt, {A}, nullptr).
extern const char kNoLinkRelPaymentMethodManifestHttpHeader[];
// Payment handler did not respond to the "paymentrequest" event. // Payment handler did not respond to the "paymentrequest" event.
extern const char kNoResponseToPaymentEvent[]; extern const char kNoResponseToPaymentEvent[];
...@@ -250,6 +241,14 @@ extern const char kCanMakePaymentEventNoExplicitlyVerifiedMethods[]; ...@@ -250,6 +241,14 @@ extern const char kCanMakePaymentEventNoExplicitlyVerifiedMethods[];
// A message about unsupported payment method. // A message about unsupported payment method.
extern const char kGenericPaymentMethodNotSupportedMessage[]; extern const char kGenericPaymentMethodNotSupportedMessage[];
// Used for errors downloading the payment method manifest. This format should
// be used with base::ReplaceStringPlaceholders(fmt, {A}, nullptr).
extern const char kNoContentAndNoLinkHeader[];
// User when the downloaded payment manifest A is empty. This format should be
// used with base::ReplaceStringPlaceholders(fmt, {A}, nullptr).
extern const char kNoContentInPaymentManifest[];
} // namespace errors } // namespace errors
} // namespace payments } // namespace payments
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/optional.h" #include "base/optional.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h" #include "base/strings/string_split.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "components/link_header_util/link_header_util.h" #include "components/link_header_util/link_header_util.h"
...@@ -33,59 +34,29 @@ ...@@ -33,59 +34,29 @@
namespace payments { namespace payments {
namespace { namespace {
GURL ParseResponseHeader(const GURL& url, // Invokes |callback| with |error_format|.
scoped_refptr<net::HttpResponseHeaders> headers, void RespondWithError(const base::StringPiece& error_format,
const GURL& final_url,
const ErrorLogger& log, const ErrorLogger& log,
std::string* out_error_message) { PaymentManifestDownloadCallback callback) {
if (!headers) { std::string error_message = base::ReplaceStringPlaceholders(
*out_error_message = base::ReplaceStringPlaceholders( error_format, {final_url.spec()}, nullptr);
errors::kNoLinkRelPaymentMethodManifestHttpHeader, {url.spec()}, log.Error(error_message);
nullptr); std::move(callback).Run(final_url, std::string(), error_message);
log.Error(*out_error_message); }
return GURL();
}
int response_code = headers->response_code();
if (response_code != net::HTTP_OK && response_code != net::HTTP_NO_CONTENT) {
*out_error_message = base::ReplaceStringPlaceholders(
errors::kHttpHeadRequestFailed, {url.spec()}, nullptr),
log.Error(*out_error_message);
return GURL();
}
std::string link_header;
headers->GetNormalizedHeader("link", &link_header);
if (link_header.empty()) {
*out_error_message = base::ReplaceStringPlaceholders(
errors::kNoLinkRelPaymentMethodManifestHttpHeader, {url.spec()},
nullptr);
log.Error(*out_error_message);
return GURL();
}
for (const auto& value : link_header_util::SplitLinkHeader(link_header)) {
std::string payment_method_manifest_url;
std::unordered_map<std::string, base::Optional<std::string>> params;
if (!link_header_util::ParseLinkHeaderValue(
value.first, value.second, &payment_method_manifest_url, &params)) {
continue;
}
auto rel = params.find("rel");
if (rel == params.end())
continue;
std::vector<std::string> rel_parts = // Invokes the |callback| with |response_body|. If |response_body| is empty,
base::SplitString(rel->second.value_or(""), HTTP_LWS, // then invokes |callback| with |empty_error_format|.
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); void RespondWithContent(const std::string& response_body,
if (base::Contains(rel_parts, "payment-method-manifest")) const base::StringPiece& empty_error_format,
return url.Resolve(payment_method_manifest_url); const GURL& final_url,
const ErrorLogger& log,
PaymentManifestDownloadCallback callback) {
if (response_body.empty()) {
RespondWithError(empty_error_format, final_url, log, std::move(callback));
} else {
std::move(callback).Run(final_url, response_body, std::string());
} }
*out_error_message = base::ReplaceStringPlaceholders(
errors::kNoLinkRelPaymentMethodManifestHttpHeader, {url.spec()}, nullptr);
log.Error(*out_error_message);
return GURL();
} }
bool IsValidManifestUrl(const GURL& url, bool IsValidManifestUrl(const GURL& url,
...@@ -126,22 +97,6 @@ GURL ParseRedirectUrl(const net::RedirectInfo& redirect_info, ...@@ -126,22 +97,6 @@ GURL ParseRedirectUrl(const net::RedirectInfo& redirect_info,
return redirect_info.new_url; return redirect_info.new_url;
} }
std::string ParseResponseContent(
const GURL& final_url,
const std::string& response_body,
scoped_refptr<net::HttpResponseHeaders> headers,
const ErrorLogger& log,
std::string* out_error_message) {
if (!headers || headers->response_code() != net::HTTP_OK) {
*out_error_message = base::ReplaceStringPlaceholders(
errors::kPaymentManifestDownloadFailed, {final_url.spec()}, nullptr);
log.Error(*out_error_message);
return std::string();
}
return response_body;
}
} // namespace } // namespace
PaymentManifestDownloader::PaymentManifestDownloader( PaymentManifestDownloader::PaymentManifestDownloader(
...@@ -160,7 +115,8 @@ void PaymentManifestDownloader::DownloadPaymentMethodManifest( ...@@ -160,7 +115,8 @@ void PaymentManifestDownloader::DownloadPaymentMethodManifest(
PaymentManifestDownloadCallback callback) { PaymentManifestDownloadCallback callback) {
DCHECK(UrlUtil::IsValidManifestUrl(url)); DCHECK(UrlUtil::IsValidManifestUrl(url));
// Restrict number of redirects for efficiency and breaking circle. // Restrict number of redirects for efficiency and breaking circle.
InitiateDownload(merchant_origin, url, "HEAD", InitiateDownload(merchant_origin, url,
Download::Type::RESPONSE_BODY_OR_LINK_HEADER,
/*allowed_number_of_redirects=*/3, std::move(callback)); /*allowed_number_of_redirects=*/3, std::move(callback));
} }
...@@ -169,7 +125,8 @@ void PaymentManifestDownloader::DownloadWebAppManifest( ...@@ -169,7 +125,8 @@ void PaymentManifestDownloader::DownloadWebAppManifest(
const GURL& url, const GURL& url,
PaymentManifestDownloadCallback callback) { PaymentManifestDownloadCallback callback) {
DCHECK(UrlUtil::IsValidManifestUrl(url)); DCHECK(UrlUtil::IsValidManifestUrl(url));
InitiateDownload(payment_method_manifest_origin, url, "GET", InitiateDownload(payment_method_manifest_origin, url,
Download::Type::RESPONSE_BODY,
/*allowed_number_of_redirects=*/0, std::move(callback)); /*allowed_number_of_redirects=*/0, std::move(callback));
} }
...@@ -195,7 +152,7 @@ void PaymentManifestDownloader::OnURLLoaderRedirect( ...@@ -195,7 +152,7 @@ void PaymentManifestDownloader::OnURLLoaderRedirect(
// Manually follow some type of redirects. // Manually follow some type of redirects.
std::string error_message; std::string error_message;
if (download->allowed_number_of_redirects > 0) { if (download->allowed_number_of_redirects > 0) {
DCHECK(download->method == "HEAD"); DCHECK_EQ(Download::Type::RESPONSE_BODY_OR_LINK_HEADER, download->type);
GURL redirect_url = ParseRedirectUrl(redirect_info, download->original_url, GURL redirect_url = ParseRedirectUrl(redirect_info, download->original_url,
*log_, &error_message); *log_, &error_message);
if (!redirect_url.is_empty()) { if (!redirect_url.is_empty()) {
...@@ -204,7 +161,8 @@ void PaymentManifestDownloader::OnURLLoaderRedirect( ...@@ -204,7 +161,8 @@ void PaymentManifestDownloader::OnURLLoaderRedirect(
download->original_url, redirect_url, download->original_url, redirect_url,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) { net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
// Redirects preserve the original request initiator. // Redirects preserve the original request initiator.
InitiateDownload(download->request_initiator, redirect_url, "HEAD", InitiateDownload(download->request_initiator, redirect_url,
Download::Type::RESPONSE_BODY_OR_LINK_HEADER,
--download->allowed_number_of_redirects, --download->allowed_number_of_redirects,
std::move(download->callback)); std::move(download->callback));
return; return;
...@@ -250,23 +208,62 @@ void PaymentManifestDownloader::OnURLLoaderCompleteInternal( ...@@ -250,23 +208,62 @@ void PaymentManifestDownloader::OnURLLoaderCompleteInternal(
downloads_.erase(download_it); downloads_.erase(download_it);
std::string error_message; std::string error_message;
if (download->method == "GET") { if (download->type == Download::Type::RESPONSE_BODY) {
std::string content = ParseResponseContent(final_url, response_body, if (!headers || headers->response_code() != net::HTTP_OK) {
headers, *log_, &error_message); RespondWithError(errors::kPaymentManifestDownloadFailed, final_url, *log_,
std::move(download->callback).Run(final_url, content, error_message); std::move(download->callback));
} else {
RespondWithContent(response_body, errors::kNoContentInPaymentManifest,
final_url, *log_, std::move(download->callback));
}
return;
}
DCHECK_EQ(Download::Type::RESPONSE_BODY_OR_LINK_HEADER, download->type);
if (!headers) {
RespondWithContent(response_body, errors::kNoContentAndNoLinkHeader,
final_url, *log_, std::move(download->callback));
return;
}
if (headers->response_code() != net::HTTP_OK &&
headers->response_code() != net::HTTP_NO_CONTENT) {
RespondWithError(errors::kPaymentManifestDownloadFailed, final_url, *log_,
std::move(download->callback));
return; return;
} }
DCHECK_EQ("HEAD", download->method); std::string link_header;
GURL payment_method_manifest_url = headers->GetNormalizedHeader("link", &link_header);
ParseResponseHeader(final_url, headers, *log_, &error_message); if (link_header.empty()) {
if (!payment_method_manifest_url.is_valid()) { RespondWithContent(response_body, errors::kNoContentAndNoLinkHeader,
std::move(download->callback).Run(final_url, std::string(), error_message); final_url, *log_, std::move(download->callback));
return; return;
} }
if (!IsValidManifestUrl(payment_method_manifest_url, *log_, &error_message)) { for (const auto& value : link_header_util::SplitLinkHeader(link_header)) {
std::move(download->callback).Run(final_url, std::string(), error_message); std::string link_url;
std::unordered_map<std::string, base::Optional<std::string>> params;
if (!link_header_util::ParseLinkHeaderValue(value.first, value.second,
&link_url, &params)) {
continue;
}
auto rel = params.find("rel");
if (rel == params.end())
continue;
std::vector<std::string> rel_parts =
base::SplitString(rel->second.value_or(""), HTTP_LWS,
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (base::Contains(rel_parts, "payment-method-manifest")) {
GURL payment_method_manifest_url = final_url.Resolve(link_url);
if (!IsValidManifestUrl(payment_method_manifest_url, *log_,
&error_message)) {
std::move(download->callback)
.Run(final_url, std::string(), error_message);
return; return;
} }
...@@ -275,16 +272,24 @@ void PaymentManifestDownloader::OnURLLoaderCompleteInternal( ...@@ -275,16 +272,24 @@ void PaymentManifestDownloader::OnURLLoaderCompleteInternal(
errors::kCrossOriginPaymentMethodManifestNotAllowed, errors::kCrossOriginPaymentMethodManifestNotAllowed,
{payment_method_manifest_url.spec(), final_url.spec()}, nullptr); {payment_method_manifest_url.spec(), final_url.spec()}, nullptr);
log_->Error(error_message); log_->Error(error_message);
std::move(download->callback).Run(final_url, std::string(), error_message); std::move(download->callback)
.Run(final_url, std::string(), error_message);
return; return;
} }
// The request initiator for the payment method manifest is the origin of the // The request initiator for the payment method manifest is the origin of
// HEAD request with the HTTP link header. // the GET request with the HTTP link header.
// https://github.com/w3c/webappsec-fetch-metadata/issues/30 // https://github.com/w3c/webappsec-fetch-metadata/issues/30
InitiateDownload( InitiateDownload(
url::Origin::Create(final_url), payment_method_manifest_url, "GET", url::Origin::Create(final_url), payment_method_manifest_url,
Download::Type::RESPONSE_BODY,
/*allowed_number_of_redirects=*/0, std::move(download->callback)); /*allowed_number_of_redirects=*/0, std::move(download->callback));
return;
}
}
RespondWithContent(response_body, errors::kNoContentAndNoLinkHeader,
final_url, *log_, std::move(download->callback));
} }
network::SimpleURLLoader* PaymentManifestDownloader::GetLoaderForTesting() { network::SimpleURLLoader* PaymentManifestDownloader::GetLoaderForTesting() {
...@@ -300,11 +305,16 @@ GURL PaymentManifestDownloader::GetLoaderOriginalURLForTesting() { ...@@ -300,11 +305,16 @@ GURL PaymentManifestDownloader::GetLoaderOriginalURLForTesting() {
void PaymentManifestDownloader::InitiateDownload( void PaymentManifestDownloader::InitiateDownload(
const url::Origin& request_initiator, const url::Origin& request_initiator,
const GURL& url, const GURL& url,
const std::string& method, Download::Type download_type,
int allowed_number_of_redirects, int allowed_number_of_redirects,
PaymentManifestDownloadCallback callback) { PaymentManifestDownloadCallback callback) {
DCHECK(UrlUtil::IsValidManifestUrl(url)); DCHECK(UrlUtil::IsValidManifestUrl(url));
// Only initial download of the payment method manifest (which might contain
// an HTTP Link header) is allowed to redirect.
DCHECK(allowed_number_of_redirects == 0 ||
download_type == Download::Type::RESPONSE_BODY_OR_LINK_HEADER);
net::NetworkTrafficAnnotationTag traffic_annotation = net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("payment_manifest_downloader", R"( net::DefineNetworkTrafficAnnotation("payment_manifest_downloader", R"(
semantics { semantics {
...@@ -328,7 +338,7 @@ void PaymentManifestDownloader::InitiateDownload( ...@@ -328,7 +338,7 @@ void PaymentManifestDownloader::InitiateDownload(
auto resource_request = std::make_unique<network::ResourceRequest>(); auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->request_initiator = request_initiator; resource_request->request_initiator = request_initiator;
resource_request->url = url; resource_request->url = url;
resource_request->method = method; resource_request->method = "GET";
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit; resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
std::unique_ptr<network::SimpleURLLoader> loader = std::unique_ptr<network::SimpleURLLoader> loader =
network::SimpleURLLoader::Create(std::move(resource_request), network::SimpleURLLoader::Create(std::move(resource_request),
...@@ -343,7 +353,7 @@ void PaymentManifestDownloader::InitiateDownload( ...@@ -343,7 +353,7 @@ void PaymentManifestDownloader::InitiateDownload(
auto download = std::make_unique<Download>(); auto download = std::make_unique<Download>();
download->request_initiator = request_initiator; download->request_initiator = request_initiator;
download->method = method; download->type = download_type;
download->original_url = url; download->original_url = url;
download->loader = std::move(loader); download->loader = std::move(loader);
download->callback = std::move(callback); download->callback = std::move(callback);
......
...@@ -37,19 +37,24 @@ class ErrorLogger; ...@@ -37,19 +37,24 @@ class ErrorLogger;
// //
// Download failure results in empty contents. Failure to download the manifest // Download failure results in empty contents. Failure to download the manifest
// can happen because of the following reasons: // can happen because of the following reasons:
// - HTTP response code is not 200. (204 is also allowed for HEAD request.) // - HTTP response code is not 200. (204 is also allowed for payment method
// - HTTP GET on the manifest URL returns empty content. // manifest.)
// //
// In the case of a payment method manifest download, can also fail when: // In the case of a payment method manifest download, can also fail when:
// - More than three redirects. // - More than three redirects.
// - Cross-site redirects. // - Cross-site redirects.
// - HTTP GET on the manifest URL returns empty content and:
// - HTTP response headers are absent. // - HTTP response headers are absent.
// - HTTP response headers do not contain Link headers. // - HTTP response headers do not contain Link headers.
// - Link header does not contain rel="payment-method-manifest". // - Link header does not contain rel="payment-method-manifest".
// - Link header does not contain a valid URL of the same origin. // - Link header does not contain a valid URL of the same origin.
// - After following the Link header:
// - There's a redirect.
// - HTTP GET returns empty content.
// //
// In the case of a web app manifest download, can also also fail when: // In the case of a web app manifest download, can also also fail when:
// - There's a redirect. // - There's a redirect.
// - HTTP GET on the manifest URL returns empty content.
using PaymentManifestDownloadCallback = using PaymentManifestDownloadCallback =
base::OnceCallback<void(const GURL& url, base::OnceCallback<void(const GURL& url,
const std::string& contents, const std::string& contents,
...@@ -59,9 +64,9 @@ using PaymentManifestDownloadCallback = ...@@ -59,9 +64,9 @@ using PaymentManifestDownloadCallback =
// payment method name that is a URL with HTTPS scheme, e.g., // payment method name that is a URL with HTTPS scheme, e.g.,
// https://bobpay.com. // https://bobpay.com.
// //
// The downloader follows up to three redirects for the HEAD request only (used // The downloader follows up to three redirects for the payment method manifest
// for payment method manifests). Three is enough for known legitimate use cases // request only. Three is enough for known legitimate use cases and seems like a
// and seems like a good upper bound. // good upper bound.
// //
// The command line must be initialized to use this class in tests, because it // The command line must be initialized to use this class in tests, because it
// checks for --unsafely-treat-insecure-origin-as-secure=<origin> flag. For // checks for --unsafely-treat-insecure-origin-as-secure=<origin> flag. For
...@@ -75,12 +80,10 @@ class PaymentManifestDownloader { ...@@ -75,12 +80,10 @@ class PaymentManifestDownloader {
virtual ~PaymentManifestDownloader(); virtual ~PaymentManifestDownloader();
// Download a payment method manifest from |url| via two consecutive HTTP // Download a payment method manifest from |url| via a GET. The HTTP response
// requests: // header is parsed for Link header. If there is no Link header, then the body
// // is returned. If there's a Link header, then it is followed exactly once.
// 1) HEAD request for the payment method name |url|. The HTTP response header // Example header:
// is parsed for Link header that points to the location of the payment
// method manifest file. Example of a relative location:
// //
// Link: <data/payment-manifest.json>; rel="payment-method-manifest" // Link: <data/payment-manifest.json>; rel="payment-method-manifest"
// //
...@@ -92,8 +95,6 @@ class PaymentManifestDownloader { ...@@ -92,8 +95,6 @@ class PaymentManifestDownloader {
// //
// The absolute location must use HTTPS scheme. // The absolute location must use HTTPS scheme.
// //
// 2) GET request for the payment method manifest file.
//
// |merchant_origin| should be the origin of the iframe that created the // |merchant_origin| should be the origin of the iframe that created the
// PaymentRequest object. It is used by security features like // PaymentRequest object. It is used by security features like
// 'Sec-Fetch-Site' and 'Cross-Origin-Resource-Policy'. // 'Sec-Fetch-Site' and 'Cross-Origin-Resource-Policy'.
...@@ -129,11 +130,16 @@ class PaymentManifestDownloader { ...@@ -129,11 +130,16 @@ class PaymentManifestDownloader {
// Information about an ongoing download request. // Information about an ongoing download request.
struct Download { struct Download {
enum class Type {
RESPONSE_BODY_OR_LINK_HEADER,
RESPONSE_BODY,
};
Download(); Download();
~Download(); ~Download();
int allowed_number_of_redirects = 0; int allowed_number_of_redirects = 0;
std::string method; Type type = Type::RESPONSE_BODY;
url::Origin request_initiator; url::Origin request_initiator;
GURL original_url; GURL original_url;
std::unique_ptr<network::SimpleURLLoader> loader; std::unique_ptr<network::SimpleURLLoader> loader;
...@@ -167,7 +173,7 @@ class PaymentManifestDownloader { ...@@ -167,7 +173,7 @@ class PaymentManifestDownloader {
// Overridden in TestDownloader. // Overridden in TestDownloader.
virtual void InitiateDownload(const url::Origin& request_initiator, virtual void InitiateDownload(const url::Origin& request_initiator,
const GURL& url, const GURL& url,
const std::string& method, Download::Type download_type,
int allowed_number_of_redirects, int allowed_number_of_redirects,
PaymentManifestDownloadCallback callback); PaymentManifestDownloadCallback callback);
......
...@@ -22,12 +22,20 @@ namespace { ...@@ -22,12 +22,20 @@ namespace {
using testing::_; using testing::_;
static const char kNoError[] = ""; static constexpr char kNoContent[] = "";
static constexpr char kNoError[] = "";
static constexpr char kNoLinkHeader[] = "";
static constexpr char kNoResponseBody[] = "";
} // namespace } // namespace
class PaymentMethodManifestDownloaderTest : public testing::Test { class PaymentMethodManifestDownloaderTest : public testing::Test {
public: protected:
enum class Headers {
kSend,
kOmit,
};
PaymentMethodManifestDownloaderTest() PaymentMethodManifestDownloaderTest()
: test_url_("https://bobpay.com"), : test_url_("https://bobpay.com"),
shared_url_loader_factory_( shared_url_loader_factory_(
...@@ -41,35 +49,34 @@ class PaymentMethodManifestDownloaderTest : public testing::Test { ...@@ -41,35 +49,34 @@ class PaymentMethodManifestDownloaderTest : public testing::Test {
base::Unretained(this))); base::Unretained(this)));
} }
~PaymentMethodManifestDownloaderTest() override {}
MOCK_METHOD3(OnManifestDownload, MOCK_METHOD3(OnManifestDownload,
void(const GURL& unused_url_after_redirects, void(const GURL& unused_url_after_redirects,
const std::string& content, const std::string& content,
const std::string& error_message)); const std::string& error_message));
void CallComplete(int response_code = 200, void ServerResponse(int response_code,
const std::string& link_header = std::string(), Headers send_headers,
const std::string& response_body = std::string(), const std::string& link_header,
bool send_headers = true) { const std::string& response_body) {
scoped_refptr<net::HttpResponseHeaders> headers; scoped_refptr<net::HttpResponseHeaders> headers;
if (send_headers) { if (send_headers == Headers::kSend) {
headers = base::MakeRefCounted<net::HttpResponseHeaders>(std::string()); headers = base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
headers->ReplaceStatusLine(base::StringPrintf( headers->ReplaceStatusLine(base::StringPrintf(
"HTTP/1.1 %d %s", response_code, "HTTP/1.1 %d %s", response_code,
net::GetHttpReasonPhrase( net::GetHttpReasonPhrase(
static_cast<net::HttpStatusCode>(response_code)))); static_cast<net::HttpStatusCode>(response_code))));
}
if (!link_header.empty()) if (!link_header.empty())
headers->AddHeader(link_header); headers->AddHeader(link_header);
}
downloader_.OnURLLoaderCompleteInternal( downloader_.OnURLLoaderCompleteInternal(
downloader_.GetLoaderForTesting(), downloader_.GetLoaderForTesting(),
downloader_.GetLoaderOriginalURLForTesting(), response_body, headers, downloader_.GetLoaderOriginalURLForTesting(), response_body, headers,
net::OK); net::OK);
} }
void CallRedirect(int redirect_code, const GURL& new_url) { void ServerRedirect(int redirect_code, const GURL& new_url) {
net::RedirectInfo redirect_info; net::RedirectInfo redirect_info;
redirect_info.status_code = redirect_code; redirect_info.status_code = redirect_code;
redirect_info.new_url = new_url; redirect_info.new_url = new_url;
...@@ -88,127 +95,247 @@ class PaymentMethodManifestDownloaderTest : public testing::Test { ...@@ -88,127 +95,247 @@ class PaymentMethodManifestDownloaderTest : public testing::Test {
network::TestURLLoaderFactory test_factory_; network::TestURLLoaderFactory test_factory_;
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_; scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
PaymentManifestDownloader downloader_; PaymentManifestDownloader downloader_;
DISALLOW_COPY_AND_ASSIGN(PaymentMethodManifestDownloaderTest);
}; };
TEST_F(PaymentMethodManifestDownloaderTest, HttpHeadResponse404IsFailure) { TEST_F(PaymentMethodManifestDownloaderTest, FirstHttpResponse404IsFailure) {
EXPECT_CALL(
*this,
OnManifestDownload(
_, kNoContent,
"Unable to download payment manifest \"https://bobpay.com/\"."));
ServerResponse(404, Headers::kSend, kNoLinkHeader, kNoResponseBody);
}
TEST_F(PaymentMethodManifestDownloaderTest,
NoHttpHeadersAndEmptyResponseBodyIsFailure) {
EXPECT_CALL(*this, EXPECT_CALL(*this,
OnManifestDownload( OnManifestDownload(
_, std::string(), _, kNoContent,
"Unable to make a HEAD request to \"https://bobpay.com/\" " "No content and no \"Link: rel=payment-method-manifest\" "
"for payment method manifest.")); "HTTP header found at \"https://bobpay.com/\"."));
CallComplete(404); ServerResponse(200, Headers::kOmit, kNoLinkHeader, kNoResponseBody);
} }
TEST_F(PaymentMethodManifestDownloaderTest, NoHttpHeadersIsFailure) { TEST_F(PaymentMethodManifestDownloaderTest,
EXPECT_CALL( NoHttpHeadersButWithResponseBodyIsSuccess) {
*this, OnManifestDownload(_, std::string(), EXPECT_CALL(*this, OnManifestDownload(_, "response body", kNoError));
"No \"Link: rel=payment-method-manifest\" HTTP "
"header found at \"https://bobpay.com/\"."));
CallComplete(200, std::string(), std::string(), false); ServerResponse(200, Headers::kOmit, kNoLinkHeader, "response body");
} }
TEST_F(PaymentMethodManifestDownloaderTest, EmptyHttpHeaderIsFailure) { TEST_F(PaymentMethodManifestDownloaderTest,
EmptyHttpHeaderAndEmptyResponseBodyIsFailure) {
EXPECT_CALL( EXPECT_CALL(
*this, OnManifestDownload(_, std::string(), *this, OnManifestDownload(
"No \"Link: rel=payment-method-manifest\" HTTP " _, kNoContent,
"No content and no \"Link: rel=payment-method-manifest\" HTTP "
"header found at \"https://bobpay.com/\".")); "header found at \"https://bobpay.com/\"."));
CallComplete(200); ServerResponse(200, Headers::kSend, kNoLinkHeader, kNoResponseBody);
} }
TEST_F(PaymentMethodManifestDownloaderTest, EmptyHttpLinkHeaderIsFailure) { TEST_F(PaymentMethodManifestDownloaderTest,
scoped_refptr<net::HttpResponseHeaders> headers( EmptyHttpHeaderButWithResponseBodyIsSuccess) {
new net::HttpResponseHeaders(std::string())); EXPECT_CALL(*this, OnManifestDownload(_, "response content", kNoError));
EXPECT_CALL(
*this, OnManifestDownload(_, std::string(),
"No \"Link: rel=payment-method-manifest\" HTTP "
"header found at \"https://bobpay.com/\"."));
CallComplete(200, "Link:"); ServerResponse(200, Headers::kSend, kNoLinkHeader, "response content");
} }
TEST_F(PaymentMethodManifestDownloaderTest, NoRelInHttpLinkHeaderIsFailure) { TEST_F(PaymentMethodManifestDownloaderTest,
EXPECT_CALL( EmptyHttpLinkHeaderWithoutResponseBodyIsFailure) {
*this, OnManifestDownload(_, std::string(), EXPECT_CALL(*this,
"No \"Link: rel=payment-method-manifest\" HTTP " OnManifestDownload(
"header found at \"https://bobpay.com/\".")); _, kNoContent,
"No content and no \"Link: rel=payment-method-manifest\" "
"HTTP header found at \"https://bobpay.com/\"."));
CallComplete(200, "Link: <manifest.json>"); ServerResponse(200, Headers::kSend, "Link:", kNoResponseBody);
} }
TEST_F(PaymentMethodManifestDownloaderTest, NoUrlInHttpLinkHeaderIsFailure) { TEST_F(PaymentMethodManifestDownloaderTest,
EXPECT_CALL( EmptyHttpLinkHeaderButWithResponseBodyIsSuccess) {
*this, OnManifestDownload(_, std::string(), EXPECT_CALL(*this, OnManifestDownload(_, "response body", kNoError));
"No \"Link: rel=payment-method-manifest\" HTTP "
"header found at \"https://bobpay.com/\"."));
CallComplete(200, "Link: rel=payment-method-manifest"); ServerResponse(200, Headers::kSend, "Link:", "response body");
} }
TEST_F(PaymentMethodManifestDownloaderTest, TEST_F(PaymentMethodManifestDownloaderTest,
NoManifestRellInHttpLinkHeaderIsFailure) { NoRelInHttpLinkHeaderAndNoResponseBodyIsFailure) {
EXPECT_CALL( EXPECT_CALL(*this,
*this, OnManifestDownload(_, std::string(), OnManifestDownload(
"No \"Link: rel=payment-method-manifest\" HTTP " _, std::string(),
"header found at \"https://bobpay.com/\".")); "No content and no \"Link: rel=payment-method-manifest\" "
"HTTP header found at \"https://bobpay.com/\"."));
ServerResponse(200, Headers::kSend, "Link: <manifest.json>", kNoResponseBody);
}
TEST_F(PaymentMethodManifestDownloaderTest,
NoRelInHttpLinkHeaderButWithResponseBodyIsSuccess) {
EXPECT_CALL(*this, OnManifestDownload(_, "response body", kNoError));
ServerResponse(200, Headers::kSend, "Link: <manifest.json>", "response body");
}
TEST_F(PaymentMethodManifestDownloaderTest,
NoUrlInHttpLinkHeaderAndNoResponseBodyIsFailure) {
EXPECT_CALL(*this,
OnManifestDownload(
_, kNoContent,
"No content and no \"Link: rel=payment-method-manifest\" "
"HTTP header found at \"https://bobpay.com/\"."));
ServerResponse(200, Headers::kSend, "Link: rel=payment-method-manifest",
kNoResponseBody);
}
TEST_F(PaymentMethodManifestDownloaderTest,
NoUrlInHttpLinkHeaderButWithResponseBodyIsSuccess) {
EXPECT_CALL(*this, OnManifestDownload(_, "response body", kNoError));
ServerResponse(200, Headers::kSend, "Link: rel=payment-method-manifest",
"response body");
}
TEST_F(PaymentMethodManifestDownloaderTest,
NoManifestRellInHttpLinkHeaderAndNoResponseBodyIsFailure) {
EXPECT_CALL(*this,
OnManifestDownload(
_, kNoContent,
"No content and no \"Link: rel=payment-method-manifest\" "
"HTTP header found at \"https://bobpay.com/\"."));
ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=web-app-manifest",
kNoResponseBody);
}
TEST_F(PaymentMethodManifestDownloaderTest,
NoManifestRellInHttpLinkHeaderButWithResponseBodyIsSuccess) {
EXPECT_CALL(*this, OnManifestDownload(_, "response body", kNoError));
CallComplete(200, "Link: <manifest.json>; rel=web-app-manifest"); ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=web-app-manifest",
"response body");
} }
TEST_F(PaymentMethodManifestDownloaderTest, HttpGetResponse404IsFailure) { TEST_F(PaymentMethodManifestDownloaderTest, SecondHttpResponse404IsFailure) {
scoped_refptr<net::HttpResponseHeaders> headers( ServerResponse(200, Headers::kSend,
new net::HttpResponseHeaders(std::string())); "Link: <manifest.json>; rel=payment-method-manifest",
CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); kNoResponseBody);
EXPECT_CALL(*this, EXPECT_CALL(*this,
OnManifestDownload(_, std::string(), OnManifestDownload(_, kNoContent,
"Unable to download payment manifest " "Unable to download payment manifest "
"\"https://bobpay.com/manifest.json\".")); "\"https://bobpay.com/manifest.json\"."));
CallComplete(404); ServerResponse(404, Headers::kSend, kNoLinkHeader, kNoResponseBody);
} }
TEST_F(PaymentMethodManifestDownloaderTest, EmptyHttpGetResponseIsFailure) { TEST_F(PaymentMethodManifestDownloaderTest, EmptySecondResponseIsFailure) {
CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this,
OnManifestDownload(_, kNoContent,
"No content found in payment manifest "
"\"https://bobpay.com/manifest.json\"."));
ServerResponse(200, Headers::kSend, kNoLinkHeader, kNoResponseBody);
}
TEST_F(PaymentMethodManifestDownloaderTest,
SecondResponseWithoutHeadersIsFailure) {
ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this, EXPECT_CALL(*this,
OnManifestDownload(_, std::string(), OnManifestDownload(_, kNoContent,
"Unable to download payment manifest " "Unable to download payment manifest "
"\"https://bobpay.com/manifest.json\".")); "\"https://bobpay.com/manifest.json\"."));
CallComplete(200, std::string(), std::string(), false); ServerResponse(200, Headers::kOmit, kNoLinkHeader, kNoResponseBody);
} }
TEST_F(PaymentMethodManifestDownloaderTest, NonEmptyHttpGetResponseIsSuccess) { TEST_F(PaymentMethodManifestDownloaderTest, NonEmptySecondResponseIsSuccess) {
CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
CallComplete(200, std::string(), "manifest content"); ServerResponse(200, Headers::kSend, kNoLinkHeader, "manifest content");
} }
TEST_F(PaymentMethodManifestDownloaderTest, HeaderResponseCode204IsSuccess) { TEST_F(PaymentMethodManifestDownloaderTest, FirstResponseCode204IsSuccess) {
CallComplete(204, "Link: <manifest.json>; rel=payment-method-manifest"); ServerResponse(204, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
CallComplete(200, std::string(), "manifest content"); ServerResponse(200, Headers::kSend, kNoLinkHeader, "manifest content");
}
TEST_F(PaymentMethodManifestDownloaderTest, SecondResponseCode204IsFailure) {
ServerResponse(204, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this,
OnManifestDownload(_, kNoContent,
"Unable to download payment manifest "
"\"https://bobpay.com/manifest.json\"."));
ServerResponse(204, Headers::kSend, kNoLinkHeader, "manifest content");
}
TEST_F(PaymentMethodManifestDownloaderTest,
SecondResponseWithLinkHeaderAndNoContentIsFailure) {
ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this,
OnManifestDownload(_, kNoContent,
"No content found in payment manifest "
"\"https://bobpay.com/manifest.json\"."));
ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
}
TEST_F(PaymentMethodManifestDownloaderTest,
SecondResponseWithLinkHeaderAndWithContentReturnsTheContent) {
ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
"manifest content");
} }
TEST_F(PaymentMethodManifestDownloaderTest, RelativeHttpHeaderLinkUrl) { TEST_F(PaymentMethodManifestDownloaderTest, RelativeHttpHeaderLinkUrl) {
CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_EQ("https://bobpay.com/manifest.json", GetOriginalURL()); EXPECT_EQ("https://bobpay.com/manifest.json", GetOriginalURL());
} }
TEST_F(PaymentMethodManifestDownloaderTest, AbsoluteHttpsHeaderLinkUrl) { TEST_F(PaymentMethodManifestDownloaderTest, AbsoluteHttpsHeaderLinkUrl) {
CallComplete(200, ServerResponse(200, Headers::kSend,
"Link: <https://bobpay.com/manifest.json>; " "Link: <https://bobpay.com/manifest.json>; "
"rel=payment-method-manifest"); "rel=payment-method-manifest",
kNoResponseBody);
EXPECT_EQ("https://bobpay.com/manifest.json", GetOriginalURL()); EXPECT_EQ("https://bobpay.com/manifest.json", GetOriginalURL());
} }
...@@ -217,131 +344,152 @@ TEST_F(PaymentMethodManifestDownloaderTest, AbsoluteHttpHeaderLinkUrl) { ...@@ -217,131 +344,152 @@ TEST_F(PaymentMethodManifestDownloaderTest, AbsoluteHttpHeaderLinkUrl) {
EXPECT_CALL( EXPECT_CALL(
*this, *this,
OnManifestDownload( OnManifestDownload(
_, std::string(), _, kNoContent,
"\"http://bobpay.com/manifest.json\" is not a valid payment manifest " "\"http://bobpay.com/manifest.json\" is not a valid payment manifest "
"URL with HTTPS scheme (or HTTP scheme for localhost).")); "URL with HTTPS scheme (or HTTP scheme for localhost)."));
CallComplete(200, ServerResponse(
"Link: <http://bobpay.com/manifest.json>; " 200, Headers::kSend,
"rel=payment-method-manifest"); "Link: <http://bobpay.com/manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
} }
TEST_F(PaymentMethodManifestDownloaderTest, 300IsUnsupportedRedirect) { TEST_F(PaymentMethodManifestDownloaderTest, 300IsUnsupportedRedirect) {
EXPECT_CALL(*this, EXPECT_CALL(*this,
OnManifestDownload( OnManifestDownload(
_, std::string(), _, kNoContent,
"HTTP status code 300 \"Multiple Choices\" not allowed for " "HTTP status code 300 \"Multiple Choices\" not allowed for "
"payment method manifest \"https://bobpay.com/\".")); "payment method manifest \"https://bobpay.com/\"."));
CallRedirect(300, GURL("https://pay.bobpay.com")); ServerRedirect(300, GURL("https://pay.bobpay.com"));
} }
TEST_F(PaymentMethodManifestDownloaderTest, 301And302AreSupportedRedirects) { TEST_F(PaymentMethodManifestDownloaderTest, 301And302AreSupportedRedirects) {
CallRedirect(301, GURL("https://pay.bobpay.com")); ServerRedirect(301, GURL("https://pay.bobpay.com"));
EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com"));
CallRedirect(302, GURL("https://newpay.bobpay.com")); ServerRedirect(302, GURL("https://newpay.bobpay.com"));
EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com"));
CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
CallComplete(200, std::string(), "manifest content"); ServerResponse(200, Headers::kSend, kNoLinkHeader, "manifest content");
}
TEST_F(PaymentMethodManifestDownloaderTest,
CannotRedirectAfterFollowingLinkHeader) {
ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this, OnManifestDownload(
_, kNoContent,
"Unable to download the payment manifest because "
"reached the maximum number of redirects."));
ServerRedirect(301, GURL("https://pay.bobpay.com"));
} }
TEST_F(PaymentMethodManifestDownloaderTest, 302And303AreSupportedRedirects) { TEST_F(PaymentMethodManifestDownloaderTest, 302And303AreSupportedRedirects) {
CallRedirect(302, GURL("https://pay.bobpay.com")); ServerRedirect(302, GURL("https://pay.bobpay.com"));
EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com"));
CallRedirect(303, GURL("https://newpay.bobpay.com")); ServerRedirect(303, GURL("https://newpay.bobpay.com"));
EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com"));
CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
CallComplete(200, std::string(), "manifest content"); ServerResponse(200, Headers::kSend, kNoLinkHeader, "manifest content");
} }
TEST_F(PaymentMethodManifestDownloaderTest, 304IsUnsupportedRedirect) { TEST_F(PaymentMethodManifestDownloaderTest, 304IsUnsupportedRedirect) {
EXPECT_CALL(*this, EXPECT_CALL(*this,
OnManifestDownload( OnManifestDownload(
_, std::string(), _, kNoContent,
"HTTP status code 304 \"Not Modified\" not allowed for " "HTTP status code 304 \"Not Modified\" not allowed for "
"payment method manifest \"https://bobpay.com/\".")); "payment method manifest \"https://bobpay.com/\"."));
CallRedirect(304, GURL("https://pay.bobpay.com")); ServerRedirect(304, GURL("https://pay.bobpay.com"));
} }
TEST_F(PaymentMethodManifestDownloaderTest, 305IsUnsupportedRedirect) { TEST_F(PaymentMethodManifestDownloaderTest, 305IsUnsupportedRedirect) {
EXPECT_CALL(*this, OnManifestDownload( EXPECT_CALL(*this, OnManifestDownload(
_, std::string(), _, kNoContent,
"HTTP status code 305 \"Use Proxy\" not allowed for " "HTTP status code 305 \"Use Proxy\" not allowed for "
"payment method manifest \"https://bobpay.com/\".")); "payment method manifest \"https://bobpay.com/\"."));
CallRedirect(305, GURL("https://pay.bobpay.com")); ServerRedirect(305, GURL("https://pay.bobpay.com"));
} }
TEST_F(PaymentMethodManifestDownloaderTest, 307And308AreSupportedRedirects) { TEST_F(PaymentMethodManifestDownloaderTest, 307And308AreSupportedRedirects) {
CallRedirect(307, GURL("https://pay.bobpay.com")); ServerRedirect(307, GURL("https://pay.bobpay.com"));
EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com"));
CallRedirect(308, GURL("https://newpay.bobpay.com")); ServerRedirect(308, GURL("https://newpay.bobpay.com"));
EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com"));
CallComplete(200, "Link: <manifest.json>; rel=payment-method-manifest"); ServerResponse(200, Headers::kSend,
"Link: <manifest.json>; rel=payment-method-manifest",
kNoResponseBody);
EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
CallComplete(200, std::string(), "manifest content"); ServerResponse(200, Headers::kSend, kNoLinkHeader, "manifest content");
} }
TEST_F(PaymentMethodManifestDownloaderTest, NoMoreThanThreeRedirects) { TEST_F(PaymentMethodManifestDownloaderTest, NoMoreThanThreeRedirects) {
CallRedirect(301, GURL("https://pay.bobpay.com")); ServerRedirect(301, GURL("https://pay.bobpay.com"));
EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://pay.bobpay.com"));
CallRedirect(302, GURL("https://oldpay.bobpay.com")); ServerRedirect(302, GURL("https://oldpay.bobpay.com"));
EXPECT_EQ(GetOriginalURL(), GURL("https://oldpay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://oldpay.bobpay.com"));
CallRedirect(308, GURL("https://newpay.bobpay.com")); ServerRedirect(308, GURL("https://newpay.bobpay.com"));
EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com")); EXPECT_EQ(GetOriginalURL(), GURL("https://newpay.bobpay.com"));
EXPECT_CALL(*this, OnManifestDownload( EXPECT_CALL(*this, OnManifestDownload(
_, std::string(), _, kNoContent,
"Unable to download the payment manifest because " "Unable to download the payment manifest because "
"reached the maximum number of redirects.")); "reached the maximum number of redirects."));
CallRedirect(308, GURL("https://newpay.bobpay.com")); ServerRedirect(308, GURL("https://newpay.bobpay.com"));
} }
TEST_F(PaymentMethodManifestDownloaderTest, InvalidRedirectUrlIsFailure) { TEST_F(PaymentMethodManifestDownloaderTest, InvalidRedirectUrlIsFailure) {
EXPECT_CALL(*this, OnManifestDownload( EXPECT_CALL(*this, OnManifestDownload(
_, std::string(), _, kNoContent,
"\"\" is not a valid payment manifest URL with HTTPS " "\"\" is not a valid payment manifest URL with HTTPS "
"scheme (or HTTP scheme for localhost).")); "scheme (or HTTP scheme for localhost)."));
CallRedirect(308, GURL("pay.bobpay.com")); ServerRedirect(308, GURL("pay.bobpay.com"));
} }
TEST_F(PaymentMethodManifestDownloaderTest, NotAllowCrossSiteRedirects) { TEST_F(PaymentMethodManifestDownloaderTest, NotAllowCrossSiteRedirects) {
EXPECT_CALL( EXPECT_CALL(
*this, *this,
OnManifestDownload( OnManifestDownload(
_, std::string(), _, kNoContent,
"Cross-site redirect from \"https://bobpay.com/\" to " "Cross-site redirect from \"https://bobpay.com/\" to "
"\"https://alicepay.com/\" not allowed for payment manifests.")); "\"https://alicepay.com/\" not allowed for payment manifests."));
CallRedirect(301, GURL("https://alicepay.com")); ServerRedirect(301, GURL("https://alicepay.com"));
} }
class WebAppManifestDownloaderTest : public testing::Test { class WebAppManifestDownloaderTest : public testing::Test {
...@@ -366,8 +514,7 @@ class WebAppManifestDownloaderTest : public testing::Test { ...@@ -366,8 +514,7 @@ class WebAppManifestDownloaderTest : public testing::Test {
const std::string& content, const std::string& content,
const std::string& error_message)); const std::string& error_message));
void CallComplete(int response_code, void ServerResponse(int response_code, const std::string& response_body) {
const std::string& response_body = std::string()) {
scoped_refptr<net::HttpResponseHeaders> headers = scoped_refptr<net::HttpResponseHeaders> headers =
base::MakeRefCounted<net::HttpResponseHeaders>(std::string()); base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
headers->ReplaceStatusLine(base::StringPrintf( headers->ReplaceStatusLine(base::StringPrintf(
...@@ -393,22 +540,26 @@ TEST_F(WebAppManifestDownloaderTest, HttpGetResponse404IsFailure) { ...@@ -393,22 +540,26 @@ TEST_F(WebAppManifestDownloaderTest, HttpGetResponse404IsFailure) {
EXPECT_CALL( EXPECT_CALL(
*this, *this,
OnManifestDownload( OnManifestDownload(
_, std::string(), _, kNoContent,
"Unable to download payment manifest \"https://bobpay.com/\".")); "Unable to download payment manifest \"https://bobpay.com/\"."));
CallComplete(404); ServerResponse(404, kNoResponseBody);
} }
TEST_F(WebAppManifestDownloaderTest, EmptyHttpGetResponseIsFailure) { TEST_F(WebAppManifestDownloaderTest, EmptyHttpGetResponseIsFailure) {
EXPECT_CALL(*this, OnManifestDownload(_, std::string(), kNoError)); EXPECT_CALL(
*this,
OnManifestDownload(
_, kNoContent,
"No content found in payment manifest \"https://bobpay.com/\"."));
CallComplete(200); ServerResponse(200, kNoResponseBody);
} }
TEST_F(WebAppManifestDownloaderTest, NonEmptyHttpGetResponseIsSuccess) { TEST_F(WebAppManifestDownloaderTest, NonEmptyHttpGetResponseIsSuccess) {
EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError)); EXPECT_CALL(*this, OnManifestDownload(_, "manifest content", kNoError));
CallComplete(200, "manifest content"); ServerResponse(200, "manifest content");
} }
} // namespace payments } // namespace payments
...@@ -29,11 +29,11 @@ void TestDownloader::AddTestServerURL(const std::string& prefix, ...@@ -29,11 +29,11 @@ void TestDownloader::AddTestServerURL(const std::string& prefix,
void TestDownloader::InitiateDownload( void TestDownloader::InitiateDownload(
const url::Origin& request_initiator, const url::Origin& request_initiator,
const GURL& url, const GURL& url,
const std::string& method, Download::Type download_type,
int allowed_number_of_redirects, int allowed_number_of_redirects,
PaymentManifestDownloadCallback callback) { PaymentManifestDownloadCallback callback) {
PaymentManifestDownloader::InitiateDownload( PaymentManifestDownloader::InitiateDownload(
request_initiator, FindTestServerURL(url), method, request_initiator, FindTestServerURL(url), download_type,
allowed_number_of_redirects, std::move(callback)); allowed_number_of_redirects, std::move(callback));
} }
......
...@@ -97,7 +97,7 @@ class TestDownloader : public PaymentManifestDownloader { ...@@ -97,7 +97,7 @@ class TestDownloader : public PaymentManifestDownloader {
// PaymentManifestDownloader implementation. // PaymentManifestDownloader implementation.
void InitiateDownload(const url::Origin& request_initiator, void InitiateDownload(const url::Origin& request_initiator,
const GURL& url, const GURL& url,
const std::string& method, Download::Type download_type,
int allowed_number_of_redirects, int allowed_number_of_redirects,
PaymentManifestDownloadCallback callback) override; PaymentManifestDownloadCallback callback) override;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment