Skip to content

Commit 26a63eb

Browse files
committed
vatin: reject a duplicated country code prefix (#420)
vatin.validate() stripped the leading country code itself and then passed the remainder to the country module, which strips its own optional country code prefix again. For a doubled prefix such as 'BE BE 0308.357.159' both strips fired, leaving a valid national number, so the VATIN validated even though stdnum.eu.vat correctly rejects it. Validate the full number with the country module (which strips its own prefix once), mirroring stdnum.eu.vat, and only fall back to stripping the country code for modules that do not recognise it - guarding that fallback so a doubled prefix is not stripped a second time. Closes #420.
1 parent 5d4ad17 commit 26a63eb

2 files changed

Lines changed: 34 additions & 3 deletions

File tree

stdnum/vatin.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,26 @@ def validate(number: str) -> str:
8787
This performs the country-specific check for the number.
8888
"""
8989
number = clean(number, '').strip()
90-
module = _get_cc_module(number[:2])
90+
cc = number[:2]
91+
module = _get_cc_module(cc)
9192
try:
92-
return number[:2].upper() + module.validate(number[2:])
93+
# Most country modules accept and strip the optional country-code
94+
# prefix themselves, so the full number is validated. Stripping the
95+
# prefix here as well would silently accept a doubled country code
96+
# such as "BE BE 0308.357.159" (see #420).
97+
result = module.validate(number)
9398
except ValidationError:
94-
return module.validate(number)
99+
# Some country modules expect the national number without the country
100+
# code prefix. Only retry that way when the remainder is not itself
101+
# prefixed with the country code, otherwise a doubled prefix would be
102+
# accepted.
103+
remainder = re.sub(r'[^0-9A-Za-z]', '', number[2:])
104+
if remainder[:2].upper() == cc.upper():
105+
raise
106+
result = module.validate(number[2:])
107+
if not result.startswith(cc.upper()):
108+
result = cc.upper() + result
109+
return result
95110

96111

97112
def is_valid(number: str) -> bool:

tests/test_vatin.doctest

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,19 @@ Check for VAT numbers that cannot be compacted without EU prefix:
102102
True
103103
>>> vatin.compact('EU191849184')
104104
'EU191849184'
105+
106+
107+
A duplicated country code prefix should not be accepted (#420). This used to
108+
pass because the country code was stripped twice (once here and once by the
109+
country module):
110+
111+
>>> vatin.is_valid('BE 0308.357.159')
112+
True
113+
>>> vatin.is_valid('BE BE 0308.357.159')
114+
False
115+
>>> vatin.is_valid('BEBE0308357159')
116+
False
117+
>>> vatin.validate('BE BE 0308.357.159')
118+
Traceback (most recent call last):
119+
...
120+
InvalidFormat: ...

0 commit comments

Comments
 (0)