    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 *      http://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.Arrays;
    020import java.util.Map;
    021import java.util.concurrent.ConcurrentHashMap;
    022
    023import org.apache.commons.validator.routines.checkdigit.IBANCheckDigit;
    024
    025/**
    026 * IBAN Validator.
    027 * @since 1.5.0
    028 */
    029public class IBANValidator {
    030
    031    private final Map<String, Validator> formatValidators;
    032
    033    /**
    034     * The validation class
    035     */
    036    public static class Validator {
    037        /*
    038         * The minimum length does not appear to be defined by the standard.
    039         * Norway is currently the shortest at 15.
    040         *
    041         * There is no standard for BBANs; they vary between countries.
    042         * But a BBAN must consist of a branch id and account number.
    043         * Each of these must be at least 2 chars (generally more) so an absolute minimum is
    044         * 4 characters for the BBAN and 8 for the IBAN.
    045         */
    046        private static final int MIN_LEN = 8;
    047        private static final int MAX_LEN = 34; // defined by [3]
    048        final String countryCode;
    049        final RegexValidator validator;
    050        final int lengthOfIBAN; // used to avoid unnecessary regex matching
    051
    052        /**
    053         * Creates the validator
    054         * @param cc the country code
    055         * @param len the length of the IBAN
    056         * @param format the regex to use to check the format
    057         */
    058        public Validator(String cc, int len, String format) {
    059            if (!(cc.length() == 2 && Character.isUpperCase(cc.charAt(0)) && Character.isUpperCase(cc.charAt(1)))) {
    060                throw new IllegalArgumentException("Invalid country Code; must be exactly 2 upper-case characters");
    061            }
    062            if (len > MAX_LEN || len < MIN_LEN) {
    063                throw new IllegalArgumentException("Invalid length parameter, must be in range "+MIN_LEN+" to "+MAX_LEN+" inclusive: " +len);
    064            }
    065            if (!format.startsWith(cc)) {
    066                throw new IllegalArgumentException("countryCode '"+cc+"' does not agree with format: " + format);
    067            }
    068            this.countryCode = cc;
    069            this.lengthOfIBAN = len;
    070            this.validator = new RegexValidator(format);
    071        }
    072    }
    073
    074    /*
    075     * Wikipedia [1] says that only uppercase is allowed.
    076     * The SWIFT PDF file [2] implies that lower case is allowed.
    077     * However there are no examples using lower-case.
    078     * Unfortunately the relevant ISO documents (ISO 13616-1) are not available for free.
    079     * The IBANCheckDigit code treats upper and lower case the same,
    080     * so any case validation has to be done in this class.
    081     *
    082     * Note: the European Payments council has a document [3] which includes a description
    083     * of the IBAN. Section 5 clearly states that only upper case is allowed.
    084     * Also the maximum length is 34 characters (including the country code),
    085     * and the length is fixed for each country.
    086     *
    087     * It looks like lower-case is permitted in BBANs, but they must be converted to
    088     * upper case for IBANs.
    089     *
    090     * [1] https://en.wikipedia.org/wiki/International_Bank_Account_Number
    091     * [2] http://www.swift.com/dsp/resources/documents/IBAN_Registry.pdf (404)
    092     * => https://www.swift.com/sites/default/files/resources/iban_registry.pdf
    093     * The above is an old version (62, Jan 2016)
    094     * As at May 2020, the current IBAN standards are located at:
    095     * https://www.swift.com/standards/data-standards/iban
    096     * [3] http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf
    097     */
    098
    099    private static final Validator[] DEFAULT_FORMATS = {
    100            new Validator("AD", 24, "AD\\d{10}[A-Z0-9]{12}"                 ), // Andorra
    101            new Validator("AE", 23, "AE\\d{21}"                             ), // United Arab Emirates (The)
    102            new Validator("AL", 28, "AL\\d{10}[A-Z0-9]{16}"                 ), // Albania
    103            new Validator("AT", 20, "AT\\d{18}"                             ), // Austria
    104            new Validator("AZ", 28, "AZ\\d{2}[A-Z]{4}[A-Z0-9]{20}"          ), // Azerbaijan
    105            new Validator("BA", 20, "BA\\d{18}"                             ), // Bosnia and Herzegovina
    106            new Validator("BE", 16, "BE\\d{14}"                             ), // Belgium
    107            new Validator("BG", 22, "BG\\d{2}[A-Z]{4}\\d{6}[A-Z0-9]{8}"     ), // Bulgaria
    108            new Validator("BH", 22, "BH\\d{2}[A-Z]{4}[A-Z0-9]{14}"          ), // Bahrain
    109            new Validator("BR", 29, "BR\\d{25}[A-Z]{1}[A-Z0-9]{1}"          ), // Brazil
    110            new Validator("BY", 28, "BY\\d{2}[A-Z0-9]{4}\\d{4}[A-Z0-9]{16}" ), // Republic of Belarus
    111            new Validator("CH", 21, "CH\\d{7}[A-Z0-9]{12}"                  ), // Switzerland
    112            new Validator("CR", 22, "CR\\d{20}"                             ), // Costa Rica
    113            new Validator("CY", 28, "CY\\d{10}[A-Z0-9]{16}"                 ), // Cyprus
    114            new Validator("CZ", 24, "CZ\\d{22}"                             ), // Czechia
    115            new Validator("DE", 22, "DE\\d{20}"                             ), // Germany
    116            new Validator("DK", 18, "DK\\d{16}"                             ), // Denmark
    117            new Validator("DO", 28, "DO\\d{2}[A-Z0-9]{4}\\d{20}"            ), // Dominican Republic
    118            new Validator("EE", 20, "EE\\d{18}"                             ), // Estonia
    119            new Validator("EG", 29, "EG\\d{27}"                             ), // Egypt
    120            new Validator("ES", 24, "ES\\d{22}"                             ), // Spain
    121            new Validator("FI", 18, "FI\\d{16}"                             ), // Finland
    122            new Validator("FO", 18, "FO\\d{16}"                             ), // Faroe Islands
    123            new Validator("FR", 27, "FR\\d{12}[A-Z0-9]{11}\\d{2}"           ), // France
    124            new Validator("GB", 22, "GB\\d{2}[A-Z]{4}\\d{14}"               ), // United Kingdom
    125            new Validator("GE", 22, "GE\\d{2}[A-Z]{2}\\d{16}"               ), // Georgia
    126            new Validator("GI", 23, "GI\\d{2}[A-Z]{4}[A-Z0-9]{15}"          ), // Gibraltar
    127            new Validator("GL", 18, "GL\\d{16}"                             ), // Greenland
    128            new Validator("GR", 27, "GR\\d{9}[A-Z0-9]{16}"                  ), // Greece
    129            new Validator("GT", 28, "GT\\d{2}[A-Z0-9]{24}"                  ), // Guatemala
    130            new Validator("HR", 21, "HR\\d{19}"                             ), // Croatia
    131            new Validator("HU", 28, "HU\\d{26}"                             ), // Hungary
    132            new Validator("IE", 22, "IE\\d{2}[A-Z]{4}\\d{14}"               ), // Ireland
    133            new Validator("IL", 23, "IL\\d{21}"                             ), // Israel
    134            new Validator("IQ", 23, "IQ\\d{2}[A-Z]{4}\\d{15}"               ), // Iraq
    135            new Validator("IS", 26, "IS\\d{24}"                             ), // Iceland
    136            new Validator("IT", 27, "IT\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}"   ), // Italy
    137            new Validator("JO", 30, "JO\\d{2}[A-Z]{4}\\d{4}[A-Z0-9]{18}"    ), // Jordan
    138            new Validator("KW", 30, "KW\\d{2}[A-Z]{4}[A-Z0-9]{22}"          ), // Kuwait
    139            new Validator("KZ", 20, "KZ\\d{5}[A-Z0-9]{13}"                  ), // Kazakhstan
    140            new Validator("LB", 28, "LB\\d{6}[A-Z0-9]{20}"                  ), // Lebanon
    141            new Validator("LC", 32, "LC\\d{2}[A-Z]{4}[A-Z0-9]{24}"          ), // Saint Lucia
    142            new Validator("LI", 21, "LI\\d{7}[A-Z0-9]{12}"                  ), // Liechtenstein
    143            new Validator("LT", 20, "LT\\d{18}"                             ), // Lithuania
    144            new Validator("LU", 20, "LU\\d{5}[A-Z0-9]{13}"                  ), // Luxembourg
    145            new Validator("LV", 21, "LV\\d{2}[A-Z]{4}[A-Z0-9]{13}"          ), // Latvia
    146            new Validator("MC", 27, "MC\\d{12}[A-Z0-9]{11}\\d{2}"           ), // Monaco
    147            new Validator("MD", 24, "MD\\d{2}[A-Z0-9]{20}"                  ), // Moldova
    148            new Validator("ME", 22, "ME\\d{20}"                             ), // Montenegro
    149            new Validator("MK", 19, "MK\\d{5}[A-Z0-9]{10}\\d{2}"            ), // Macedonia
    150            new Validator("MR", 27, "MR\\d{25}"                             ), // Mauritania
    151            new Validator("MT", 31, "MT\\d{2}[A-Z]{4}\\d{5}[A-Z0-9]{18}"    ), // Malta
    152            new Validator("MU", 30, "MU\\d{2}[A-Z]{4}\\d{19}[A-Z]{3}"       ), // Mauritius
    153            new Validator("NL", 18, "NL\\d{2}[A-Z]{4}\\d{10}"               ), // Netherlands (The)
    154            new Validator("NO", 15, "NO\\d{13}"                             ), // Norway
    155            new Validator("PK", 24, "PK\\d{2}[A-Z]{4}[A-Z0-9]{16}"          ), // Pakistan
    156            new Validator("PL", 28, "PL\\d{26}"                             ), // Poland
    157            new Validator("PS", 29, "PS\\d{2}[A-Z]{4}[A-Z0-9]{21}"          ), // Palestine, State of
    158            new Validator("PT", 25, "PT\\d{23}"                             ), // Portugal
    159            new Validator("QA", 29, "QA\\d{2}[A-Z]{4}[A-Z0-9]{21}"          ), // Qatar
    160            new Validator("RO", 24, "RO\\d{2}[A-Z]{4}[A-Z0-9]{16}"          ), // Romania
    161            new Validator("RS", 22, "RS\\d{20}"                             ), // Serbia
    162            new Validator("SA", 24, "SA\\d{4}[A-Z0-9]{18}"                  ), // Saudi Arabia
    163            new Validator("SC", 31, "SC\\d{2}[A-Z]{4}\\d{20}[A-Z]{3}"       ), // Seychelles
    164            new Validator("SE", 24, "SE\\d{22}"                             ), // Sweden
    165            new Validator("SI", 19, "SI\\d{17}"                             ), // Slovenia
    166            new Validator("SK", 24, "SK\\d{22}"                             ), // Slovakia
    167            new Validator("SM", 27, "SM\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}"   ), // San Marino
    168            new Validator("ST", 25, "ST\\d{23}"                             ), // Sao Tome and Principe
    169            new Validator("SV", 28, "SV\\d{2}[A-Z]{4}\\d{20}"               ), // El Salvador
    170            new Validator("TL", 23, "TL\\d{21}"                             ), // Timor-Leste
    171            new Validator("TN", 24, "TN\\d{22}"                             ), // Tunisia
    172            new Validator("TR", 26, "TR\\d{8}[A-Z0-9]{16}"                  ), // Turkey
    173            new Validator("UA", 29, "UA\\d{8}[A-Z0-9]{19}"                  ), // Ukraine
    174            new Validator("VA", 22, "VA\\d{20}"                             ), // Vatican City State
    175            new Validator("VG", 24, "VG\\d{2}[A-Z]{4}\\d{16}"               ), // Virgin Islands
    176            new Validator("XK", 20, "XK\\d{18}"                             ), // Kosovo
    177    };
    178
    179    /** The singleton instance which uses the default formats */
    180    public static final IBANValidator DEFAULT_IBAN_VALIDATOR = new IBANValidator();
    181
    182    /**
    183     * Return a singleton instance of the IBAN validator using the default formats
    184     *
    185     * @return A singleton instance of the ISBN validator
    186     */
    187    public static IBANValidator getInstance() {
    188        return DEFAULT_IBAN_VALIDATOR;
    189    }
    190
    191    /**
    192     * Create a default IBAN validator.
    193     */
    194    public IBANValidator() {
    195        this(DEFAULT_FORMATS);
    196    }
    197
    198    /**
    199     * Create an IBAN validator from the specified map of IBAN formats.
    200     *
    201     * @param formatMap map of IBAN formats
    202     */
    203    public IBANValidator(Validator[] formatMap) {
    204        this.formatValidators = createValidators(formatMap);
    205    }
    206
    207    private Map<String, Validator> createValidators(Validator[] formatMap) {
    208        Map<String, Validator> m = new ConcurrentHashMap<String, Validator>();
    209        for(Validator v : formatMap) {
    210            m.put(v.countryCode, v);
    211        }
    212        return m;
    213    }
    214
    215    /**
    216     * Validate an IBAN Code
    217     *
    218     * @param code The value validation is being performed on
    219     * @return <code>true</code> if the value is valid
    220     */
    221    public boolean isValid(String code) {
    222        Validator formatValidator = getValidator(code);
    223        if (formatValidator == null || code.length() != formatValidator.lengthOfIBAN || !formatValidator.validator.isValid(code)) {
    224            return false;
    225        }
    226        return IBANCheckDigit.IBAN_CHECK_DIGIT.isValid(code);
    227    }
    228
    229    /**
    230     * Does the class have the required validator?
    231     *
    232     * @param code the code to check
    233     * @return true if there is a validator
    234     */
    235    public boolean hasValidator(String code) {
    236        return getValidator(code) != null;
    237    }
    238
    239    /**
    240     * Gets a copy of the default Validators.
    241     *
    242     * @return a copy of the default Validator array
    243     */
    244    public Validator[] getDefaultValidators() {
    245        return Arrays.copyOf(DEFAULT_FORMATS, DEFAULT_FORMATS.length);
    246    }
    247
    248    /**
    249     * Get the Validator for a given IBAN
    250     *
    251     * @param code a string starting with the ISO country code (e.g. an IBAN)
    252     *
    253     * @return the validator or {@code null} if there is not one registered.
    254     */
    255    public Validator getValidator(String code) {
    256        if (code == null || code.length() < 2) { // ensure we can extract the code
    257            return null;
    258        }
    259        String key = code.substring(0, 2);
    260        return formatValidators.get(key);
    261    }
    262
    263    /**
    264     * Installs a validator.
    265     * Will replace any existing entry which has the same countryCode
    266     *
    267     * @param validator the instance to install.
    268     * @return the previous Validator, or {@code null} if there was none
    269     * @throws IllegalStateException if an attempt is made to modify the singleton validator
    270     */
    271    public Validator setValidator(Validator validator) {
    272        if (this == DEFAULT_IBAN_VALIDATOR) {
    273            throw new IllegalStateException("The singleton validator cannot be modified");
    274        }
    275        return formatValidators.put(validator.countryCode, validator);
    276    }
    277
    278    /**
    279     * Installs a validator.
    280     * Will replace any existing entry which has the same countryCode.
    281     *
    282     * @param countryCode the country code
    283     * @param length the length of the IBAN. Must be &ge; 8 and &le; 32.
    284     * If the length is &lt; 0, the validator is removed, and the format is not used.
    285     * @param format the format of the IBAN (as a regular expression)
    286     * @return the previous Validator, or {@code null} if there was none
    287     * @throws IllegalArgumentException if there is a problem
    288     * @throws IllegalStateException if an attempt is made to modify the singleton validator
    289     */
    290    public Validator setValidator(String countryCode, int length, String format) {
    291        if (this == DEFAULT_IBAN_VALIDATOR) {
    292            throw new IllegalStateException("The singleton validator cannot be modified");
    293        }
    294        if (length < 0) {
    295            return formatValidators.remove(countryCode);
    296        }
    297        return setValidator(new Validator(countryCode, length, format));
    298    }
    299}