Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fd58870

Browse filesBrowse files
committed
[Validator] Simplified IBAN validation algorithm
1 parent 97243bc commit fd58870
Copy full SHA for fd58870

File tree

2 files changed

+82
-21
lines changed
Filter options

2 files changed

+82
-21
lines changed

‎src/Symfony/Component/Validator/Constraints/IbanValidator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Constraints/IbanValidator.php
+80-21Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313

1414
use Symfony\Component\Validator\Constraint;
1515
use Symfony\Component\Validator\ConstraintValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
1617

1718
/**
1819
* @author Manuel Reinhard <manu@sprain.ch>
1920
* @author Michael Schummel
21+
* @author Bernhard Schussek <bschussek@gmail.com>
2022
* @link http://www.michael-schummel.de/2007/10/05/iban-prufung-mit-php/
2123
*/
2224
class IbanValidator extends ConstraintValidator
@@ -30,41 +32,98 @@ public function validate($value, Constraint $constraint)
3032
return;
3133
}
3234

33-
// An IBAN without a country code is not an IBAN.
34-
if (0 === preg_match('/[A-Z]/', $value)) {
35-
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
35+
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
36+
throw new UnexpectedTypeException($value, 'string');
37+
}
38+
39+
// Remove spaces
40+
$canonicalized = str_replace(' ', '', $value);
41+
42+
if (strlen($canonicalized) < 4) {
43+
$this->context->addViolation($constraint->message, array(
44+
'{{ value }}' => $value,
45+
));
3646

3747
return;
3848
}
3949

40-
$teststring = preg_replace('/\s+/', '', $value);
41-
42-
if (strlen($teststring) < 4) {
43-
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
50+
// The IBAN must have at least 4 characters, start with a country
51+
// code and contain only digits and (uppercase) characters
52+
if (strlen($canonicalized) < 4 || !ctype_upper($canonicalized{0})
53+
|| !ctype_upper($canonicalized{1}) || !ctype_alnum($canonicalized)) {
54+
$this->context->addViolation($constraint->message, array(
55+
'{{ value }}' => $value,
56+
));
4457

4558
return;
4659
}
4760

48-
$teststring = substr($teststring, 4)
49-
.strval(ord($teststring{0}) - 55)
50-
.strval(ord($teststring{1}) - 55)
51-
.substr($teststring, 2, 2);
61+
// Move the first four characters to the end
62+
// e.g. CH93 0076 2011 6238 5295 7
63+
// -> 0076 2011 6238 5295 7 CH93
64+
$canonicalized = substr($canonicalized, 4).substr($canonicalized, 0, 4);
5265

53-
$teststring = preg_replace_callback('/[A-Z]/', function ($letter) {
54-
return intval(ord(strtolower($letter[0])) - 87);
55-
}, $teststring);
66+
// Convert all remaining letters to their ordinals
67+
// The result is an integer, which is too large for PHP's int
68+
// data type, so we store it in a string instead.
69+
// e.g. 0076 2011 6238 5295 7 CH93
70+
// -> 0076 2011 6238 5295 7 121893
71+
$checkSum = $this->toBigInt($canonicalized);
5672

57-
$rest = 0;
58-
$strlen = strlen($teststring);
59-
for ($pos = 0; $pos < $strlen; $pos += 7) {
60-
$part = strval($rest).substr($teststring, $pos, 7);
61-
$rest = intval($part) % 97;
73+
if (false === $checkSum) {
74+
$this->context->addViolation($constraint->message, array(
75+
'{{ value }}' => $value,
76+
));
77+
78+
return;
6279
}
6380

64-
if ($rest != 1) {
65-
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
81+
// Do a modulo-97 operation on the large integer
82+
// We cannot use PHP's modulo operator, so we calculate the
83+
// modulo step-wisely instead
84+
if (1 !== $this->bigModulo97($checkSum)) {
85+
$this->context->addViolation($constraint->message, array(
86+
'{{ value }}' => $value,
87+
));
6688

6789
return;
6890
}
6991
}
92+
93+
private function toBigInt($string)
94+
{
95+
$chars = str_split($string);
96+
$bigInt = '';
97+
98+
foreach ($chars as $char) {
99+
// Convert uppercase characters to ordinals, starting with 10 for "A"
100+
if (ctype_upper($char)) {
101+
$bigInt .= (ord($char) - 55);
102+
103+
continue;
104+
}
105+
106+
// Disallow lowercase characters
107+
if (ctype_lower($char)) {
108+
return false;
109+
}
110+
111+
// Simply append digits
112+
$bigInt .= $char;
113+
}
114+
115+
return $bigInt;
116+
}
117+
118+
private function bigModulo97($bigInt)
119+
{
120+
$parts = str_split($bigInt, 7);
121+
$rest = 0;
122+
123+
foreach ($parts as $part) {
124+
$rest = ($rest.$part) % 97;
125+
}
126+
127+
return $rest;
128+
}
70129
}

‎src/Symfony/Component/Validator/Tests/Constraints/IbanValidatorTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Tests/Constraints/IbanValidatorTest.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function getValidIbans()
5454
{
5555
return array(
5656
array('CH9300762011623852957'), // Switzerland without spaces
57+
array('CH93 0076 2011 6238 5295 7'), // Switzerland with multiple spaces
5758

5859
//Country list
5960
//http://www.rbs.co.uk/corporate/international/g0/guide-to-international-business/regulatory-information/iban/iban-example.ashx
@@ -182,6 +183,7 @@ public function getInvalidIbans()
182183
array('foo'),
183184
array('123'),
184185
array('0750447346'),
186+
array('CH930076201162385295]'),
185187

186188
//Ibans with lower case values are invalid
187189
array('Ae260211000000230064016'),

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.