001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.validator.routines; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.List; 022import java.util.concurrent.ConcurrentHashMap; 023import java.util.concurrent.ConcurrentMap; 024 025import org.apache.commons.validator.routines.checkdigit.IBANCheckDigit; 026 027/** 028 * IBAN Validator. 029 * <p> 030 * Checks an IBAN for: 031 * </p> 032 * <ul> 033 * <li>country code prefix</li> 034 * <li>IBAN length</li> 035 * <li>pattern (digits and/or uppercase letters)</li> 036 * <li>IBAN Checkdigits (using {@link IBANCheckDigit})</li> 037 * </ul> 038 * The class does not perform checks on the embedded BBAN (Basic Bank Account Number). 039 * Each country has its own rules for these. 040 * <p> 041 * The validator includes a default set of formats derived from the IBAN registry at 042 * https://www.swift.com/standards/data-standards/iban. 043 * </p> 044 * <p> 045 * This can get out of date, but the set can be adjusted by creating a validator and using the 046 * {@link #setValidator(String, int, String)} or 047 * {@link #setValidator(Validator)} 048 * method to add (or remove) an entry. 049 * </p> 050 * <p> 051 * For example: 052 * </p> 053 * <pre> 054 * IBANValidator ibv = new IBANValidator(); 055 * ibv.setValidator("XX", 12, "XX\\d{10}") 056 * </pre> 057 * <p> 058 * The singleton default instance cannot be modified in this way. 059 * </p> 060 * 061 * @since 1.5.0 062 */ 063public class IBANValidator { 064 065 /** 066 * The validation class 067 */ 068 public static class Validator { 069 070 /** 071 * The minimum length does not appear to be defined by the standard. 072 * Norway is currently the shortest at 15. 073 * 074 * There is no standard for BBANs; they vary between countries. 075 * But a BBAN must consist of a branch id and account number. 076 * Each of these must be at least 2 chars (generally more) so an absolute minimum is 077 * 4 characters for the BBAN and 8 for the IBAN. 078 */ 079 private static final int MIN_LEN = 8; 080 private static final int MAX_LEN = 34; // defined by [3] 081 082 final String countryCode; 083 final String[] otherCountryCodes; 084 final RegexValidator regexValidator; 085 086 /** 087 * Used to avoid unnecessary regex matching. 088 */ 089 private final int ibanLength; 090 091 /** 092 * Creates the validator. 093 * 094 * @param countryCode The country code. 095 * @param ibanLength The length of the IBAN. 096 * @param regexWithCC The regex to use to check the format, the regex MUST start with the country code. 097 */ 098 public Validator(final String countryCode, final int ibanLength, final String regexWithCC) { 099 this(countryCode, ibanLength, regexWithCC.substring(countryCode.length()), new String[] {}); 100 } 101 102 /** 103 * Creates the validator. 104 * 105 * @param countryCode The country code. 106 * @param ibanLength The length of the IBAN. 107 * @param regexWithoutCC The regex to use to check the format, the regex MUST NOT start with the country code. 108 */ 109 Validator(final String countryCode, final int ibanLength, final String regexWithoutCC, final String... otherCountryCodes) { 110 if (!(countryCode.length() == 2 && Character.isUpperCase(countryCode.charAt(0)) && Character.isUpperCase(countryCode.charAt(1)))) { 111 throw new IllegalArgumentException("Invalid country Code; must be exactly 2 upper-case characters"); 112 } 113 if (ibanLength > MAX_LEN || ibanLength < MIN_LEN) { 114 throw new IllegalArgumentException("Invalid length parameter, must be in range " + MIN_LEN + " to " + MAX_LEN + " inclusive: " + ibanLength); 115 } 116 this.countryCode = countryCode; 117 this.otherCountryCodes = otherCountryCodes.clone(); 118 final List<String> regexList = new ArrayList<>(this.otherCountryCodes.length + 1); 119 regexList.add(countryCode + regexWithoutCC); 120 for (final String otherCc : otherCountryCodes) { 121 regexList.add(otherCc + regexWithoutCC); 122 } 123 this.ibanLength = ibanLength; 124 this.regexValidator = new RegexValidator(regexList); 125 } 126 127 /** 128 * Gets the length. 129 * 130 * @return The length. 131 * @since 1.10.0 132 */ 133 public int getIbanLength() { 134 return ibanLength; 135 } 136 137 /** 138 * Gets the RegexValidator. 139 * 140 * @return The RegexValidator. 141 * @since 1.8 142 */ 143 public RegexValidator getRegexValidator() { 144 return regexValidator; 145 } 146 } 147 148 private static final int SHORT_CODE_LEN = 2; 149 150 /* 151 * Note: the IBAN PDF registry file implies that IBANs can contain lower-case letters. 152 * However, several other documents state that IBANs must be upper-case only. 153 * See the comment block following this array. 154 * 155 * In the Regexes below, only upper-case is used. 156 */ 157 private static final Validator[] DEFAULT_VALIDATORS = { 158 // @formatter:off 159 new Validator("AD", 24, "AD\\d{10}[A-Z0-9]{12}"), // Andorra 160 new Validator("AE", 23, "AE\\d{21}"), // United Arab Emirates (The) 161 new Validator("AL", 28, "AL\\d{10}[A-Z0-9]{16}"), // Albania 162 new Validator("AT", 20, "AT\\d{18}"), // Austria 163 new Validator("AZ", 28, "AZ\\d{2}[A-Z]{4}[A-Z0-9]{20}"), // Azerbaijan 164 new Validator("BA", 20, "BA\\d{18}"), // Bosnia and Herzegovina 165 new Validator("BE", 16, "BE\\d{14}"), // Belgium 166 new Validator("BG", 22, "BG\\d{2}[A-Z]{4}\\d{6}[A-Z0-9]{8}"), // Bulgaria 167 new Validator("BH", 22, "BH\\d{2}[A-Z]{4}[A-Z0-9]{14}"), // Bahrain 168 new Validator("BI", 27, "BI\\d{25}"), // Burundi 169 new Validator("BR", 29, "BR\\d{25}[A-Z]{1}[A-Z0-9]{1}"), // Brazil 170 new Validator("BY", 28, "BY\\d{2}[A-Z0-9]{4}\\d{4}[A-Z0-9]{16}"), // Republic of Belarus 171 new Validator("CH", 21, "CH\\d{7}[A-Z0-9]{12}"), // Switzerland 172 new Validator("CR", 22, "CR\\d{20}"), // Costa Rica 173 new Validator("CY", 28, "CY\\d{10}[A-Z0-9]{16}"), // Cyprus 174 new Validator("CZ", 24, "CZ\\d{22}"), // Czechia 175 new Validator("DE", 22, "DE\\d{20}"), // Germany 176 new Validator("DJ", 27, "DJ\\d{25}"), // Djibouti 177 new Validator("DK", 18, "DK\\d{16}"), // Denmark 178 new Validator("DO", 28, "DO\\d{2}[A-Z0-9]{4}\\d{20}"), // Dominican Republic 179 new Validator("EE", 20, "EE\\d{18}"), // Estonia 180 new Validator("EG", 29, "EG\\d{27}"), // Egypt 181 new Validator("ES", 24, "ES\\d{22}"), // Spain 182 new Validator("FI", 18, "\\d{16}", "AX"), // Finland 183 new Validator("FK", 18, "FK\\d{2}[A-Z]{2}\\d{12}"), // Falkland Islands, since Jul-23 184 new Validator("FO", 18, "FO\\d{16}"), // Faroe Islands 185 new Validator("FR", 27, "\\d{12}[A-Z0-9]{11}\\d{2}", "GF", "GP", "MQ", "RE", "PF", "TF", "YT", "NC", "BL", "MF", "PM", "WF"), // France 186 new Validator("GB", 22, "\\d{2}[A-Z]{4}\\d{14}", "IM", "JE", "GG"), // United Kingdom 187 new Validator("GE", 22, "GE\\d{2}[A-Z]{2}\\d{16}"), // Georgia 188 new Validator("GI", 23, "GI\\d{2}[A-Z]{4}[A-Z0-9]{15}"), // Gibraltar 189 new Validator("GL", 18, "GL\\d{16}"), // Greenland 190 new Validator("GR", 27, "GR\\d{9}[A-Z0-9]{16}"), // Greece 191 new Validator("GT", 28, "GT\\d{2}[A-Z0-9]{24}"), // Guatemala 192 new Validator("HN", 28, "HN\\d{2}[A-Z]{4}\\d{20}"), // Honduras, since Dec-24 193 new Validator("HR", 21, "HR\\d{19}"), // Croatia 194 new Validator("HU", 28, "HU\\d{26}"), // Hungary 195 new Validator("IE", 22, "IE\\d{2}[A-Z]{4}\\d{14}"), // Ireland 196 new Validator("IL", 23, "IL\\d{21}"), // Israel 197 new Validator("IQ", 23, "IQ\\d{2}[A-Z]{4}\\d{15}"), // Iraq 198 new Validator("IS", 26, "IS\\d{24}"), // Iceland 199 new Validator("IT", 27, "IT\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}"), // Italy 200 new Validator("JO", 30, "JO\\d{2}[A-Z]{4}\\d{4}[A-Z0-9]{18}"), // Jordan 201 new Validator("KW", 30, "KW\\d{2}[A-Z]{4}[A-Z0-9]{22}"), // Kuwait 202 new Validator("KZ", 20, "KZ\\d{5}[A-Z0-9]{13}"), // Kazakhstan 203 new Validator("LB", 28, "LB\\d{6}[A-Z0-9]{20}"), // Lebanon 204 new Validator("LC", 32, "LC\\d{2}[A-Z]{4}[A-Z0-9]{24}"), // Saint Lucia 205 new Validator("LI", 21, "LI\\d{7}[A-Z0-9]{12}"), // Liechtenstein 206 new Validator("LT", 20, "LT\\d{18}"), // Lithuania 207 new Validator("LU", 20, "LU\\d{5}[A-Z0-9]{13}"), // Luxembourg 208 new Validator("LV", 21, "LV\\d{2}[A-Z]{4}[A-Z0-9]{13}"), // Latvia 209 new Validator("LY", 25, "LY\\d{23}"), // Libya 210 new Validator("MC", 27, "MC\\d{12}[A-Z0-9]{11}\\d{2}"), // Monaco 211 new Validator("MD", 24, "MD\\d{2}[A-Z0-9]{20}"), // Moldova 212 new Validator("ME", 22, "ME\\d{20}"), // Montenegro 213 new Validator("MK", 19, "MK\\d{5}[A-Z0-9]{10}\\d{2}"), // Macedonia 214 new Validator("MN", 20, "MN\\d{18}"), // Mongolia, since Apr-23 215 new Validator("MR", 27, "MR\\d{25}"), // Mauritania 216 new Validator("MT", 31, "MT\\d{2}[A-Z]{4}\\d{5}[A-Z0-9]{18}"), // Malta 217 new Validator("MU", 30, "MU\\d{2}[A-Z]{4}\\d{19}[A-Z]{3}"), // Mauritius 218 new Validator("NI", 28, "NI\\d{2}[A-Z]{4}\\d{20}"), // Nicaragua, since Apr-23 219 new Validator("NL", 18, "NL\\d{2}[A-Z]{4}\\d{10}"), // Netherlands (The) 220 new Validator("NO", 15, "NO\\d{13}"), // Norway 221 new Validator("OM", 23, "OM\\d{5}[A-Z0-9]{16}"), // Oman, since Mar-24 222 new Validator("PK", 24, "PK\\d{2}[A-Z]{4}[A-Z0-9]{16}"), // Pakistan 223 new Validator("PL", 28, "PL\\d{26}"), // Poland 224 new Validator("PS", 29, "PS\\d{2}[A-Z]{4}[A-Z0-9]{21}"), // Palestine, State of 225 new Validator("PT", 25, "PT\\d{23}"), // Portugal 226 new Validator("QA", 29, "QA\\d{2}[A-Z]{4}[A-Z0-9]{21}"), // Qatar 227 new Validator("RO", 24, "RO\\d{2}[A-Z]{4}[A-Z0-9]{16}"), // Romania 228 new Validator("RS", 22, "RS\\d{20}"), // Serbia 229 new Validator("RU", 33, "RU\\d{16}[A-Z0-9]{15}"), // Russia 230 new Validator("SA", 24, "SA\\d{4}[A-Z0-9]{18}"), // Saudi Arabia 231 new Validator("SC", 31, "SC\\d{2}[A-Z]{4}\\d{20}[A-Z]{3}"), // Seychelles 232 new Validator("SD", 18, "SD\\d{16}"), // Sudan 233 new Validator("SE", 24, "SE\\d{22}"), // Sweden 234 new Validator("SI", 19, "SI\\d{17}"), // Slovenia 235 new Validator("SK", 24, "SK\\d{22}"), // Slovakia 236 new Validator("SM", 27, "SM\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}"), // San Marino 237 new Validator("SO", 23, "SO\\d{21}"), // Somalia, since Feb-23 238 new Validator("ST", 25, "ST\\d{23}"), // Sao Tome and Principe 239 new Validator("SV", 28, "SV\\d{2}[A-Z]{4}\\d{20}"), // El Salvador 240 new Validator("TL", 23, "TL\\d{21}"), // Timor-Leste 241 new Validator("TN", 24, "TN\\d{22}"), // Tunisia 242 new Validator("TR", 26, "TR\\d{8}[A-Z0-9]{16}"), // Turkey 243 new Validator("UA", 29, "UA\\d{8}[A-Z0-9]{19}"), // Ukraine 244 new Validator("VA", 22, "VA\\d{20}"), // Vatican City State 245 new Validator("VG", 24, "VG\\d{2}[A-Z]{4}\\d{16}"), // Virgin Islands 246 new Validator("XK", 20, "XK\\d{18}"), // Kosovo 247 new Validator("YE", 30, "YE\\d{2}[A-Z]{4}\\d{4}[A-Z0-9]{18}"), // Yemen 248 // @formatter:on 249 }; 250 251 /* 252 * Wikipedia [1] says that only uppercase is allowed. 253 * The SWIFT PDF file [2] implies that lower case is allowed. 254 * However, there are no examples using lower-case. 255 * Unfortunately the relevant ISO documents (ISO 13616-1) are not available for free. 256 * The IBANCheckDigit code treats upper and lower case the same, 257 * so any case validation has to be done in this class. 258 * 259 * Note: the European Payments council has a document [3] which includes a description 260 * of the IBAN. Section 5 clearly states that only upper case is allowed. 261 * Also, the maximum length is 34 characters (including the country code), 262 * and the length is fixed for each country. 263 * 264 * It looks like lower-case is permitted in BBANs, but they must be converted to 265 * upper case for IBANs. 266 * 267 * [1] https://en.wikipedia.org/wiki/International_Bank_Account_Number 268 * [2] http://www.swift.com/dsp/resources/documents/IBAN_Registry.pdf (404) 269 * => https://www.swift.com/sites/default/files/resources/iban_registry.pdf 270 * The above is an old version (62, Jan 2016) 271 * As of May 2020, the current IBAN standards are located at: 272 * https://www.swift.com/standards/data-standards/iban 273 * The above page contains links for the PDF and TXT (CSV) versions of the registry 274 * Warning: these may not agree -- in the past there have been discrepancies. 275 * The TXT file can be used to determine changes which can be cross-checked in the PDF file. 276 * [3] http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf 277 */ 278 279 /** The singleton instance which uses the default formats */ 280 public static final IBANValidator DEFAULT_IBAN_VALIDATOR = new IBANValidator(); 281 282 /** 283 * Gets the singleton instance of the IBAN validator using the default formats. 284 * 285 * @return A singleton instance of the IBAN validator. 286 */ 287 public static IBANValidator getInstance() { 288 return DEFAULT_IBAN_VALIDATOR; 289 } 290 291 private final ConcurrentMap<String, Validator> validatorMap; 292 293 /** 294 * Create a default IBAN validator. 295 */ 296 public IBANValidator() { 297 this(DEFAULT_VALIDATORS); 298 } 299 300 /** 301 * Create an IBAN validator from the specified map of IBAN formats. 302 * 303 * @param validators map of IBAN formats. 304 */ 305 public IBANValidator(final Validator[] validators) { 306 this.validatorMap = createValidators(validators); 307 } 308 309 private ConcurrentMap<String, Validator> createValidators(final Validator[] validators) { 310 final ConcurrentMap<String, Validator> map = new ConcurrentHashMap<>(); 311 for (final Validator validator : validators) { 312 map.put(validator.countryCode, validator); 313 for (final String otherCC : validator.otherCountryCodes) { 314 map.put(otherCC, validator); 315 } 316 } 317 return map; 318 } 319 320 /** 321 * Gets a copy of the default Validators. 322 * 323 * @return A copy of the default Validator array. 324 */ 325 public Validator[] getDefaultValidators() { 326 return Arrays.copyOf(DEFAULT_VALIDATORS, DEFAULT_VALIDATORS.length); 327 } 328 329 /** 330 * Gets the Validator for a given IBAN. 331 * 332 * @param code A string starting with the ISO country code (for example, an IBAN). 333 * @return The validator or {@code null} if there is not one registered. 334 */ 335 public Validator getValidator(final String code) { 336 if (code == null || code.length() < SHORT_CODE_LEN) { // ensure we can extract the code 337 return null; 338 } 339 final String key = code.substring(0, SHORT_CODE_LEN); 340 return validatorMap.get(key); 341 } 342 343 /** 344 * Tests if the given code has a validator. 345 * 346 * @param code The code to check. 347 * @return true if there is a validator. 348 */ 349 public boolean hasValidator(final String code) { 350 return getValidator(code) != null; 351 } 352 353 /** 354 * Tests an IBAN Code. 355 * 356 * @param code The value validation is being performed on. 357 * @return {@code true} if the value is valid. 358 */ 359 public boolean isValid(final String code) { 360 return validate(code) == IBANValidatorStatus.VALID; 361 } 362 363 /** 364 * Installs a validator. Will replace any existing entry which has the same countryCode. 365 * 366 * @param countryCode The country code. 367 * @param length The length of the IBAN. Must be ≥ 8 and ≤ 32. If the length is < 0, the validator is removed, and the format is not used. 368 * @param format The format of the IBAN (as a regular expression). 369 * @return The previous Validator, or {@code null} if there was none. 370 * @throws IllegalArgumentException if there is a problem. 371 * @throws IllegalStateException if an attempt is made to modify the singleton validator. 372 */ 373 public Validator setValidator(final String countryCode, final int length, final String format) { 374 if (this == DEFAULT_IBAN_VALIDATOR) { 375 throw new IllegalStateException("The singleton validator cannot be modified"); 376 } 377 if (length < 0) { 378 final Validator prev = validatorMap.remove(countryCode); 379 if (prev != null) { 380 for (final String otherCC : prev.otherCountryCodes) { 381 validatorMap.remove(otherCC); 382 } 383 } 384 return prev; 385 } 386 return setValidator(new Validator(countryCode, length, format)); 387 } 388 389 /** 390 * Installs a validator. Will replace any existing entry which has the same countryCode. 391 * 392 * @param validator The instance to install. 393 * @return The previous Validator, or {@code null} if there was none. 394 * @throws IllegalStateException if an attempt is made to modify the singleton validator. 395 */ 396 public Validator setValidator(final Validator validator) { 397 if (this == DEFAULT_IBAN_VALIDATOR) { 398 throw new IllegalStateException("The singleton validator cannot be modified"); 399 } 400 final Validator prev = validatorMap.put(validator.countryCode, validator); 401 if (prev != null) { 402 for (final String otherCC : prev.otherCountryCodes) { 403 validatorMap.remove(otherCC); 404 } 405 } 406 for (final String otherCC : validator.otherCountryCodes) { 407 validatorMap.put(otherCC, validator); 408 } 409 return prev; 410 } 411 412 /** 413 * Validate an IBAN Code. 414 * 415 * @param code The value validation is being performed on. 416 * @return {@link IBANValidatorStatus} for validation. 417 * @since 1.10.0 418 */ 419 public IBANValidatorStatus validate(final String code) { 420 final Validator formatValidator = getValidator(code); 421 if (formatValidator == null) { 422 return IBANValidatorStatus.UNKNOWN_COUNTRY; 423 } 424 if (code.length() != formatValidator.ibanLength) { 425 return IBANValidatorStatus.INVALID_LENGTH; 426 } 427 if (!formatValidator.regexValidator.isValid(code)) { 428 return IBANValidatorStatus.INVALID_PATTERN; 429 } 430 return IBANCheckDigit.IBAN_CHECK_DIGIT.isValid(code) ? IBANValidatorStatus.VALID : IBANValidatorStatus.INVALID_CHECKSUM; 431 } 432}