blob: 86d43d621dfab7f3e6ebc96278e3395187595122 [file] [log] [blame]
// Copyright 2016 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 "third_party/blink/renderer/modules/payments/payments_validators.h"
#include "third_party/blink/renderer/bindings/core/v8/script_regexp.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_address_errors.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_payer_errors.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_payment_validation_errors.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/string_resource.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
#include "third_party/blink/renderer/platform/wtf/text/string_impl.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
// Passing a giant string through IPC to the browser can cause a crash due to
// failure in memory allocation. This number here is chosen conservatively.
static constexpr size_t kMaximumStringLength = 2 * 1024;
bool PaymentsValidators::IsValidCurrencyCodeFormat(
const String& code,
String* optional_error_message) {
auto* regexp = MakeGarbageCollected<ScriptRegexp>(
"^[A-Z]{3}$", kTextCaseUnicodeInsensitive);
if (regexp->Match(code) == 0)
return true;
if (optional_error_message) {
*optional_error_message = "'" + code +
"' is not a valid ISO 4217 currency code, should "
"be well-formed 3-letter alphabetic code.";
}
return false;
}
bool PaymentsValidators::IsValidAmountFormat(const String& amount,
const String& item_name,
String* optional_error_message) {
auto* regexp = MakeGarbageCollected<ScriptRegexp>("^-?[0-9]+(\\.[0-9]+)?$",
kTextCaseSensitive);
if (regexp->Match(amount) == 0)
return true;
if (optional_error_message) {
*optional_error_message =
"'" + amount + "' is not a valid amount format for " + item_name;
}
return false;
}
bool PaymentsValidators::IsValidCountryCodeFormat(
const String& code,
String* optional_error_message) {
auto* regexp =
MakeGarbageCollected<ScriptRegexp>("^[A-Z]{2}$", kTextCaseSensitive);
if (regexp->Match(code) == 0)
return true;
if (optional_error_message)
*optional_error_message = "'" + code +
"' is not a valid CLDR country code, should be 2 "
"upper case letters [A-Z]";
return false;
}
bool PaymentsValidators::IsValidShippingAddress(
const payments::mojom::blink::PaymentAddressPtr& address,
String* optional_error_message) {
return IsValidCountryCodeFormat(address->country, optional_error_message);
}
bool PaymentsValidators::IsValidErrorMsgFormat(const String& error,
String* optional_error_message) {
if (error.length() <= kMaximumStringLength)
return true;
if (optional_error_message) {
*optional_error_message =
String::Format("Error message should be at most %zu characters long",
kMaximumStringLength);
}
return false;
}
// static
bool PaymentsValidators::IsValidAddressErrorsFormat(
const AddressErrors* errors,
String* optional_error_message) {
return (!errors->hasAddressLine() ||
IsValidErrorMsgFormat(errors->addressLine(),
optional_error_message)) &&
(!errors->hasCity() ||
IsValidErrorMsgFormat(errors->city(), optional_error_message)) &&
(!errors->hasCountry() ||
IsValidErrorMsgFormat(errors->country(), optional_error_message)) &&
(!errors->hasDependentLocality() ||
IsValidErrorMsgFormat(errors->dependentLocality(),
optional_error_message)) &&
(!errors->hasOrganization() ||
IsValidErrorMsgFormat(errors->organization(),
optional_error_message)) &&
(!errors->hasPhone() ||
IsValidErrorMsgFormat(errors->phone(), optional_error_message)) &&
(!errors->hasPostalCode() ||
IsValidErrorMsgFormat(errors->postalCode(),
optional_error_message)) &&
(!errors->hasRecipient() ||
IsValidErrorMsgFormat(errors->recipient(), optional_error_message)) &&
(!errors->hasRegion() ||
IsValidErrorMsgFormat(errors->region(), optional_error_message)) &&
(!errors->hasSortingCode() ||
IsValidErrorMsgFormat(errors->sortingCode(), optional_error_message));
}
// static
bool PaymentsValidators::IsValidPayerErrorsFormat(
const PayerErrors* errors,
String* optional_error_message) {
return (!errors->hasEmail() ||
IsValidErrorMsgFormat(errors->email(), optional_error_message)) &&
(!errors->hasName() ||
IsValidErrorMsgFormat(errors->name(), optional_error_message)) &&
(!errors->hasPhone() ||
IsValidErrorMsgFormat(errors->phone(), optional_error_message));
}
// static
bool PaymentsValidators::IsValidPaymentValidationErrorsFormat(
const PaymentValidationErrors* errors,
String* optional_error_message) {
return (!errors->hasError() ||
IsValidErrorMsgFormat(errors->error(), optional_error_message)) &&
(!errors->hasPayer() ||
IsValidPayerErrorsFormat(errors->payer(), optional_error_message)) &&
(!errors->hasShippingAddress() ||
IsValidAddressErrorsFormat(errors->shippingAddress(),
optional_error_message));
}
bool PaymentsValidators::IsValidMethodFormat(const String& identifier) {
KURL url(NullURL(), identifier);
if (!url.IsValid()) {
// Syntax for a valid standardized PMI:
// https://www.w3.org/TR/payment-method-id/#dfn-syntax-of-a-standardized-payment-method-identifier
auto* regexp = MakeGarbageCollected<ScriptRegexp>(
"^[a-z]+[0-9a-z]*(-[a-z]+[0-9a-z]*)*$", kTextCaseSensitive);
return regexp->Match(identifier) == 0;
}
// URL PMI validation rules:
// https://www.w3.org/TR/payment-method-id/#dfn-validate-a-url-based-payment-method-identifier
if (!url.User().IsEmpty() || !url.Pass().IsEmpty())
return false;
if (url.Protocol() == "https")
return true;
if (url.Protocol() != "http")
return false;
return SecurityOrigin::Create(url)->IsPotentiallyTrustworthy();
}
void PaymentsValidators::ValidateAndStringifyObject(
v8::Isolate* isolate,
const ScriptValue& input,
String& output,
ExceptionState& exception_state) {
v8::Local<v8::String> value;
if (input.IsEmpty() || !input.V8Value()->IsObject() ||
!v8::JSON::Stringify(isolate->GetCurrentContext(),
input.V8Value().As<v8::Object>())
.ToLocal(&value)) {
exception_state.ThrowTypeError(
"PaymentRequest objects should be JSON-serializable objects");
return;
}
output = ToBlinkString<String>(value, kDoNotExternalize);
// Implementation defined constant controlling the allowed JSON length.
static constexpr size_t kMaxJSONStringLength = 1024 * 1024;
if (output.length() > kMaxJSONStringLength) {
exception_state.ThrowTypeError(
String::Format("JSON serialization of PaymentRequest objects should be "
"no longer than %zu characters",
kMaxJSONStringLength));
}
}
} // namespace blink