diff --git a/stdnum/vatin.py b/stdnum/vatin.py index 3e7e2f56..1ef6743c 100644 --- a/stdnum/vatin.py +++ b/stdnum/vatin.py @@ -87,11 +87,26 @@ def validate(number: str) -> str: This performs the country-specific check for the number. """ number = clean(number, '').strip() - module = _get_cc_module(number[:2]) + cc = number[:2] + module = _get_cc_module(cc) try: - return number[:2].upper() + module.validate(number[2:]) + # Most country modules accept and strip the optional country-code + # prefix themselves, so the full number is validated. Stripping the + # prefix here as well would silently accept a doubled country code + # such as "BE BE 0308.357.159" (see #420). + result = module.validate(number) except ValidationError: - return module.validate(number) + # Some country modules expect the national number without the country + # code prefix. Only retry that way when the remainder is not itself + # prefixed with the country code, otherwise a doubled prefix would be + # accepted. + remainder = re.sub(r'[^0-9A-Za-z]', '', number[2:]) + if remainder[:2].upper() == cc.upper(): + raise + result = module.validate(number[2:]) + if not result.startswith(cc.upper()): + result = cc.upper() + result + return result def is_valid(number: str) -> bool: diff --git a/tests/test_vatin.doctest b/tests/test_vatin.doctest index 1c65db3e..30bd3cc6 100644 --- a/tests/test_vatin.doctest +++ b/tests/test_vatin.doctest @@ -102,3 +102,19 @@ Check for VAT numbers that cannot be compacted without EU prefix: True >>> vatin.compact('EU191849184') 'EU191849184' + + +A duplicated country code prefix should not be accepted (#420). This used to +pass because the country code was stripped twice (once here and once by the +country module): + +>>> vatin.is_valid('BE 0308.357.159') +True +>>> vatin.is_valid('BE BE 0308.357.159') +False +>>> vatin.is_valid('BEBE0308357159') +False +>>> vatin.validate('BE BE 0308.357.159') +Traceback (most recent call last): + ... +InvalidFormat: ...