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.match(L"si56 0231 2001 5226 972", 0, SIZE_MAX, match_case_insensitive));
Assert::IsTrue(p.is_valid); Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"SI56 0231 2001 5226 9720", 0, SIZE_MAX)); 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.match(L"...SI56 0231 2001 5226 972...", 3, SIZE_MAX));
Assert::IsTrue(p.is_valid); Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"SI56 0231 2001 5226 972", 0, SIZE_MAX)); // no-break space Assert::IsTrue(p.match(L"SI56 0231 2001 5226 972", 0, SIZE_MAX)); // no-break space
Assert::IsTrue(p.is_valid); 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.match("si56 0231 2001 5226 972", 0, SIZE_MAX, match_case_insensitive));
Assert::IsTrue(p.is_valid); Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match("SI56 0231 2001 5226 9720", 0, SIZE_MAX)); 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.match("...SI56 0231 2001 5226 972...", 3, SIZE_MAX));
Assert::IsTrue(p.is_valid); Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match("SI56&nbsp;0231&nbsp;2001&nbsp;5226&nbsp;972", 0, SIZE_MAX)); 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 { { 'X', 'K' }, {}, 20 }, // Kosovo
}; };
const country_t* country_desc = nullptr; const country_t* country_desc = nullptr;
size_t n; size_t n, available, next, bban_length;
uint32_t nominator;
this->interval.end = start; this->interval.end = start;
for (size_t i = 0; i < 2; ++i, ++this->interval.end) { for (size_t i = 0; i < 2; ++i, ++this->interval.end) {
@ -4844,32 +4845,28 @@ namespace stdex
(country_desc->check_digits[1] && this->check_digits[1] != country_desc->check_digits[1])) (country_desc->check_digits[1] && this->check_digits[1] != country_desc->check_digits[1]))
goto error; // unexpected check digits goto error; // unexpected check digits
for (n = 0; ;) { bban_length = country_desc->length - 4;
if (m_space && m_space->match(text, this->interval.end, end, flags)) for (n = 0; n < bban_length;) {
this->interval.end = m_space->interval.end;
for (size_t j = 0; j < 4; ++j) {
if (this->interval.end >= end || !text[this->interval.end]) if (this->interval.end >= end || !text[this->interval.end])
goto out; goto error; // bban too short
if (m_space && m_space->match(text, this->interval.end, end, flags)) {
this->interval.end = m_space->interval.end;
continue;
}
T chr = case_insensitive ? ctype.toupper(text[this->interval.end]) : text[this->interval.end]; T chr = case_insensitive ? ctype.toupper(text[this->interval.end]) : text[this->interval.end];
if (('0' <= chr && chr <= '9') || ('A' <= chr && chr <= 'Z')) { if (('0' <= chr && chr <= '9') || ('A' <= chr && chr <= 'Z')) {
if (n >= _countof(this->bban) - 1)
goto error; // bban overflow
this->bban[n++] = chr; this->bban[n++] = chr;
this->interval.end++; this->interval.end++;
} }
else else
goto out; goto error; // invalid bban
}
} }
out: out:
if (n < 11)
goto error; // bban too short (shorter than Norwegian)
this->bban[n] = 0; this->bban[n] = 0;
if (n + 4 == country_desc->length) {
// Normalize IBAN. // Normalize IBAN.
T normalized[69]; T normalized[69];
size_t available = 0; available = 0;
for (size_t i = 0; ; ++i) { for (size_t i = 0; ; ++i) {
if (!this->bban[i]) { if (!this->bban[i]) {
for (i = 0; i < 2; ++i) { for (i = 0; i < 2; ++i) {
@ -4908,21 +4905,17 @@ namespace stdex
} }
// Calculate modulo 97. // Calculate modulo 97.
size_t next; nominator = stdex::strtou32(normalized, 9, &next, 10);
uint32_t nominator = stdex::strtou32(normalized, 9, &next, 10);
for (;;) { for (;;) {
nominator %= 97; nominator %= 97;
if (!normalized[next]) { if (!normalized[next]) {
this->is_valid = nominator == 1; this->is_valid = nominator == 1;
break; 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) for (; digit_count < 9 && normalized[next]; ++next, ++digit_count)
nominator = nominator * 10 + (normalized[next] - '0'); nominator = nominator * 10 + (normalized[next] - '0');
} }
}
else
this->is_valid = false;
this->interval.start = start; this->interval.start = start;
return true; return true;
@ -5074,7 +5067,7 @@ namespace stdex
this->is_valid = nominator == 1; this->is_valid = nominator == 1;
break; 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) for (; digit_count < 9 && normalized[next]; ++next, ++digit_count)
nominator = nominator * 10 + (normalized[next] - '0'); nominator = nominator * 10 + (normalized[next] - '0');
} }