[Validator] Simplified IBAN validation algorithm
This commit is contained in:
parent
97243bcd02
commit
fd58870ac0
@ -13,10 +13,12 @@ namespace Symfony\Component\Validator\Constraints;
|
|||||||
|
|
||||||
use Symfony\Component\Validator\Constraint;
|
use Symfony\Component\Validator\Constraint;
|
||||||
use Symfony\Component\Validator\ConstraintValidator;
|
use Symfony\Component\Validator\ConstraintValidator;
|
||||||
|
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Manuel Reinhard <manu@sprain.ch>
|
* @author Manuel Reinhard <manu@sprain.ch>
|
||||||
* @author Michael Schummel
|
* @author Michael Schummel
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
* @link http://www.michael-schummel.de/2007/10/05/iban-prufung-mit-php/
|
* @link http://www.michael-schummel.de/2007/10/05/iban-prufung-mit-php/
|
||||||
*/
|
*/
|
||||||
class IbanValidator extends ConstraintValidator
|
class IbanValidator extends ConstraintValidator
|
||||||
@ -30,41 +32,98 @@ class IbanValidator extends ConstraintValidator
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// An IBAN without a country code is not an IBAN.
|
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
|
||||||
if (0 === preg_match('/[A-Z]/', $value)) {
|
throw new UnexpectedTypeException($value, 'string');
|
||||||
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
|
}
|
||||||
|
|
||||||
|
// Remove spaces
|
||||||
|
$canonicalized = str_replace(' ', '', $value);
|
||||||
|
|
||||||
|
if (strlen($canonicalized) < 4) {
|
||||||
|
$this->context->addViolation($constraint->message, array(
|
||||||
|
'{{ value }}' => $value,
|
||||||
|
));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$teststring = preg_replace('/\s+/', '', $value);
|
// The IBAN must have at least 4 characters, start with a country
|
||||||
|
// code and contain only digits and (uppercase) characters
|
||||||
if (strlen($teststring) < 4) {
|
if (strlen($canonicalized) < 4 || !ctype_upper($canonicalized{0})
|
||||||
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
|
|| !ctype_upper($canonicalized{1}) || !ctype_alnum($canonicalized)) {
|
||||||
|
$this->context->addViolation($constraint->message, array(
|
||||||
|
'{{ value }}' => $value,
|
||||||
|
));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$teststring = substr($teststring, 4)
|
// Move the first four characters to the end
|
||||||
.strval(ord($teststring{0}) - 55)
|
// e.g. CH93 0076 2011 6238 5295 7
|
||||||
.strval(ord($teststring{1}) - 55)
|
// -> 0076 2011 6238 5295 7 CH93
|
||||||
.substr($teststring, 2, 2);
|
$canonicalized = substr($canonicalized, 4).substr($canonicalized, 0, 4);
|
||||||
|
|
||||||
$teststring = preg_replace_callback('/[A-Z]/', function ($letter) {
|
// Convert all remaining letters to their ordinals
|
||||||
return intval(ord(strtolower($letter[0])) - 87);
|
// The result is an integer, which is too large for PHP's int
|
||||||
}, $teststring);
|
// data type, so we store it in a string instead.
|
||||||
|
// e.g. 0076 2011 6238 5295 7 CH93
|
||||||
|
// -> 0076 2011 6238 5295 7 121893
|
||||||
|
$checkSum = $this->toBigInt($canonicalized);
|
||||||
|
|
||||||
$rest = 0;
|
if (false === $checkSum) {
|
||||||
$strlen = strlen($teststring);
|
$this->context->addViolation($constraint->message, array(
|
||||||
for ($pos = 0; $pos < $strlen; $pos += 7) {
|
'{{ value }}' => $value,
|
||||||
$part = strval($rest).substr($teststring, $pos, 7);
|
));
|
||||||
$rest = intval($part) % 97;
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($rest != 1) {
|
// Do a modulo-97 operation on the large integer
|
||||||
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
|
// We cannot use PHP's modulo operator, so we calculate the
|
||||||
|
// modulo step-wisely instead
|
||||||
|
if (1 !== $this->bigModulo97($checkSum)) {
|
||||||
|
$this->context->addViolation($constraint->message, array(
|
||||||
|
'{{ value }}' => $value,
|
||||||
|
));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function toBigInt($string)
|
||||||
|
{
|
||||||
|
$chars = str_split($string);
|
||||||
|
$bigInt = '';
|
||||||
|
|
||||||
|
foreach ($chars as $char) {
|
||||||
|
// Convert uppercase characters to ordinals, starting with 10 for "A"
|
||||||
|
if (ctype_upper($char)) {
|
||||||
|
$bigInt .= (ord($char) - 55);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow lowercase characters
|
||||||
|
if (ctype_lower($char)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simply append digits
|
||||||
|
$bigInt .= $char;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bigInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function bigModulo97($bigInt)
|
||||||
|
{
|
||||||
|
$parts = str_split($bigInt, 7);
|
||||||
|
$rest = 0;
|
||||||
|
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$rest = ($rest.$part) % 97;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rest;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ class IbanValidatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
array('CH9300762011623852957'), // Switzerland without spaces
|
array('CH9300762011623852957'), // Switzerland without spaces
|
||||||
|
array('CH93 0076 2011 6238 5295 7'), // Switzerland with multiple spaces
|
||||||
|
|
||||||
//Country list
|
//Country list
|
||||||
//http://www.rbs.co.uk/corporate/international/g0/guide-to-international-business/regulatory-information/iban/iban-example.ashx
|
//http://www.rbs.co.uk/corporate/international/g0/guide-to-international-business/regulatory-information/iban/iban-example.ashx
|
||||||
@ -182,6 +183,7 @@ class IbanValidatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
array('foo'),
|
array('foo'),
|
||||||
array('123'),
|
array('123'),
|
||||||
array('0750447346'),
|
array('0750447346'),
|
||||||
|
array('CH930076201162385295]'),
|
||||||
|
|
||||||
//Ibans with lower case values are invalid
|
//Ibans with lower case values are invalid
|
||||||
array('Ae260211000000230064016'),
|
array('Ae260211000000230064016'),
|
||||||
|
Reference in New Issue
Block a user