diff --git a/stdnum/vatin.py b/stdnum/vatin.py index 3e7e2f56..c9b73ca0 100644 --- a/stdnum/vatin.py +++ b/stdnum/vatin.py @@ -87,11 +87,29 @@ 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].upper() + 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 number can be validated as it is. + result = module.validate(number) + if not result.upper().startswith(cc): + result = cc + result except ValidationError: - return module.validate(number) + # Other country modules only accept the national number, so retry + # with the country code prefix removed. + national = number[2:] + result = module.validate(national) + # If the country module stripped a second country code off the + # national number then the prefix was duplicated and the number is + # not valid (see #420). A national number that legitimately starts + # with the country code, such as the Mexican RFC MXDE111111GR2, + # keeps its prefix when compacted and is accepted. + if (re.sub(r'[^0-9A-Za-z]', '', national)[:2].upper() == cc and + not module.compact(national).upper().startswith(cc)): + raise InvalidFormat() + result = cc + result + return result def is_valid(number: str) -> bool: diff --git a/tests/test_vatin.doctest b/tests/test_vatin.doctest index ca8becbe..578f7588 100644 --- a/tests/test_vatin.doctest +++ b/tests/test_vatin.doctest @@ -104,7 +104,26 @@ True 'EU191849184' -Check seemingly double country codes are handled correctly: +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: ... + + +Check seemingly double country codes are handled correctly. A national number +may legitimately start with the country code, in which case it is kept: >>> vatin.validate('MX MXDE 111111 GR2') # the second "MX" is part of the number 'MXMXDE111111GR2' +>>> vatin.validate('MXDE 111111 GR2') # here "MX" is only the country code +'MXDE111111GR2'