parser: refine IBAN checking

Allow arbitrary spacing, minor optimizations...

Signed-off-by: Simon Rozman <simon@rozman.si>
This commit is contained in:
Simon Rozman 2023-09-19 21:40:36 +02:00
parent b5984ea8f2
commit 613bba9e05
2 changed files with 102 additions and 72 deletions

View File

@ -205,11 +205,47 @@ namespace UnitTests
Assert::IsTrue(p.match(L"si56 0231 2001 5226 972", 0, SIZE_MAX, match_case_insensitive));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"SI56 0231 2001 5226 9720", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::AreEqual(stdex::interval<size_t>(0, 23), p.interval);
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"...SI56 0231 2001 5226 972...", 3, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"SI56 0231 2001 5226 972", 0, SIZE_MAX)); // no-break space
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"BE71 0961 2345 6769", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"BR15 0000 0000 0000 1093 2840 814 P2", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"CR99 0000 0000 0000 8888 88", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match(L"FR76 3000 6000 0112 3456 7890 189", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"IE12 BOFI 9000 0112 3456 78", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match(L"DE91 1000 0000 0123 4567 89", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"GR96 0810 0010 0000 0123 4567 890", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"MU43 BOMM 0101 1234 5678 9101 000 MUR", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"PK70 BANK 0000 1234 5678 9000", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"PL10 1050 0099 7603 1234 5678 9123", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"RO09 BCYP 0000 0012 3456 7890", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"LC14 BOSL 1234 5678 9012 3456 7890 1234", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"SA44 2000 0001 2345 6789 1234", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"ES79 2100 0813 6101 2345 6789", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"SE87 3000 0000 0101 2345 6789", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match(L"CH56 0483 5012 3456 7800 9", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"GB98 MIDL 0700 9312 3456 78", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
}
{
@ -322,7 +358,8 @@ namespace UnitTests
Assert::IsTrue(p.match("si56 0231 2001 5226 972", 0, SIZE_MAX, match_case_insensitive));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match("SI56 0231 2001 5226 9720", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::AreEqual(stdex::interval<size_t>(0, 23), p.interval);
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match("...SI56 0231 2001 5226 972...", 3, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match("SI56&nbsp;0231&nbsp;2001&nbsp;5226&nbsp;972", 0, SIZE_MAX));

View File

@ -4806,7 +4806,8 @@ namespace stdex
{ { 'X', 'K' }, {}, 20 }, // Kosovo
};
const country_t* country_desc = nullptr;
size_t n;
size_t n, available, next, bban_length;
uint32_t nominator;
this->interval.end = start;
for (size_t i = 0; i < 2; ++i, ++this->interval.end) {
@ -4844,85 +4845,77 @@ namespace stdex
(country_desc->check_digits[1] && this->check_digits[1] != country_desc->check_digits[1]))
goto error; // unexpected check digits
for (n = 0; ;) {
if (m_space && m_space->match(text, this->interval.end, end, flags))
bban_length = country_desc->length - 4;
for (n = 0; n < bban_length;) {
if (this->interval.end >= end || !text[this->interval.end])
goto error; // bban too short
if (m_space && m_space->match(text, this->interval.end, end, flags)) {
this->interval.end = m_space->interval.end;
for (size_t j = 0; j < 4; ++j) {
if (this->interval.end >= end || !text[this->interval.end])
goto out;
T chr = case_insensitive ? ctype.toupper(text[this->interval.end]) : text[this->interval.end];
if (('0' <= chr && chr <= '9') || ('A' <= chr && chr <= 'Z')) {
if (n >= _countof(this->bban) - 1)
goto error; // bban overflow
this->bban[n++] = chr;
this->interval.end++;
}
else
goto out;
continue;
}
T chr = case_insensitive ? ctype.toupper(text[this->interval.end]) : text[this->interval.end];
if (('0' <= chr && chr <= '9') || ('A' <= chr && chr <= 'Z')) {
this->bban[n++] = chr;
this->interval.end++;
}
else
goto error; // invalid bban
}
out:
if (n < 11)
goto error; // bban too short (shorter than Norwegian)
this->bban[n] = 0;
if (n + 4 == country_desc->length) {
// Normalize IBAN.
T normalized[69];
size_t available = 0;
for (size_t i = 0; ; ++i) {
if (!this->bban[i]) {
for (i = 0; i < 2; ++i) {
if ('A' <= this->country[i] && this->country[i] <= 'J') {
normalized[available++] = '1';
normalized[available++] = '0' + this->country[i] - 'A';
}
else if ('K' <= this->country[i] && this->country[i] <= 'T') {
normalized[available++] = '2';
normalized[available++] = '0' + this->country[i] - 'K';
}
else if ('U' <= this->country[i] && this->country[i] <= 'Z') {
normalized[available++] = '3';
normalized[available++] = '0' + this->country[i] - 'U';
}
// Normalize IBAN.
T normalized[69];
available = 0;
for (size_t i = 0; ; ++i) {
if (!this->bban[i]) {
for (i = 0; i < 2; ++i) {
if ('A' <= this->country[i] && this->country[i] <= 'J') {
normalized[available++] = '1';
normalized[available++] = '0' + this->country[i] - 'A';
}
else if ('K' <= this->country[i] && this->country[i] <= 'T') {
normalized[available++] = '2';
normalized[available++] = '0' + this->country[i] - 'K';
}
else if ('U' <= this->country[i] && this->country[i] <= 'Z') {
normalized[available++] = '3';
normalized[available++] = '0' + this->country[i] - 'U';
}
normalized[available++] = this->check_digits[0];
normalized[available++] = this->check_digits[1];
normalized[available] = 0;
break;
}
if ('0' <= this->bban[i] && this->bban[i] <= '9')
normalized[available++] = this->bban[i];
else if ('A' <= this->bban[i] && this->bban[i] <= 'J') {
normalized[available++] = '1';
normalized[available++] = '0' + this->bban[i] - 'A';
}
else if ('K' <= this->bban[i] && this->bban[i] <= 'T') {
normalized[available++] = '2';
normalized[available++] = '0' + this->bban[i] - 'K';
}
else if ('U' <= this->bban[i] && this->bban[i] <= 'Z') {
normalized[available++] = '3';
normalized[available++] = '0' + this->bban[i] - 'U';
}
normalized[available++] = this->check_digits[0];
normalized[available++] = this->check_digits[1];
normalized[available] = 0;
break;
}
// Calculate modulo 97.
size_t next;
uint32_t nominator = stdex::strtou32(normalized, 9, &next, 10);
for (;;) {
nominator %= 97;
if (!normalized[next]) {
this->is_valid = nominator == 1;
break;
}
size_t digit_count = nominator < 10 ? 1 : 2;
for (; digit_count < 9 && normalized[next]; ++next, ++digit_count)
nominator = nominator * 10 + (normalized[next] - '0');
if ('0' <= this->bban[i] && this->bban[i] <= '9')
normalized[available++] = this->bban[i];
else if ('A' <= this->bban[i] && this->bban[i] <= 'J') {
normalized[available++] = '1';
normalized[available++] = '0' + this->bban[i] - 'A';
}
else if ('K' <= this->bban[i] && this->bban[i] <= 'T') {
normalized[available++] = '2';
normalized[available++] = '0' + this->bban[i] - 'K';
}
else if ('U' <= this->bban[i] && this->bban[i] <= 'Z') {
normalized[available++] = '3';
normalized[available++] = '0' + this->bban[i] - 'U';
}
}
else
this->is_valid = false;
// Calculate modulo 97.
nominator = stdex::strtou32(normalized, 9, &next, 10);
for (;;) {
nominator %= 97;
if (!normalized[next]) {
this->is_valid = nominator == 1;
break;
}
size_t digit_count = nominator == 0 ? 0 : nominator < 10 ? 1 : 2;
for (; digit_count < 9 && normalized[next]; ++next, ++digit_count)
nominator = nominator * 10 + (normalized[next] - '0');
}
this->interval.start = start;
return true;
@ -5074,7 +5067,7 @@ namespace stdex
this->is_valid = nominator == 1;
break;
}
size_t digit_count = nominator < 10 ? 1 : 2;
size_t digit_count = nominator == 0 ? 0 : nominator < 10 ? 1 : 2;
for (; digit_count < 9 && normalized[next]; ++next, ++digit_count)
nominator = nominator * 10 + (normalized[next] - '0');
}