View Javadoc

    1   /*
    2    * $Id: EmailValidator.java 367161 2006-01-09 02:19:23Z niallp $
    3    * $Rev: 367161 $
    4    * $Date: 2006-01-09 02:19:23 +0000 (Mon, 09 Jan 2006) $
    5    *
    6    * ====================================================================
    7    * Copyright 2001-2006 The Apache Software Foundation
    8    *
    9    * Licensed under the Apache License, Version 2.0 (the "License");
    10   * you may not use this file except in compliance with the License.
    11   * You may obtain a copy of the License at
    12   *
    13   *     http://www.apache.org/licenses/LICENSE-2.0
    14   *
    15   * Unless required by applicable law or agreed to in writing, software
    16   * distributed under the License is distributed on an "AS IS" BASIS,
    17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    18   * See the License for the specific language governing permissions and
    19   * limitations under the License.
    20   */
    21
    22  package org.apache.commons.validator;
    23
    24  import org.apache.oro.text.perl.Perl5Util;
    25
    26  /***
    27   * <p>Perform email validations.</p>
    28   * <p>
    29   * This class is a Singleton; you can retrieve the instance via the getInstance() method.
    30   * </p>
    31   * <p>
    32   * Based on a script by <a href="mailto:stamhankar@hotmail.com">Sandeep V. Tamhankar</a>
    33   * http://javascript.internet.com
    34   * </p>
    35   * <p>
    36   * This implementation is not guaranteed to catch all possible errors in an email address.
    37   * For example, an address like nobody@noplace.somedog will pass validator, even though there
    38   * is no TLD "somedog"
    39   * </p>.
    40   * @since Validator 1.1
    41   */
    42  public class EmailValidator {
    43
    44      private static final String SPECIAL_CHARS = "//(//)<>@,;:'//////\"//.//[//]";
    45      private static final String VALID_CHARS = "[^//s" + SPECIAL_CHARS + "]";
    46      private static final String QUOTED_USER = "(\"[^\"]*\")";
    47      private static final String ATOM = VALID_CHARS + '+';
    48      private static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")";
    49
    50      // Each pattern must be surrounded by /
    51      private static final String LEGAL_ASCII_PATTERN = "/^[//000-//177]+$/";
    52      private static final String EMAIL_PATTERN = "/^(.+)@(.+)$/";
    53      private static final String IP_DOMAIN_PATTERN =
    54              "/^//[(//d{1,3})[.](//d{1,3})[.](//d{1,3})[.](//d{1,3})//]$/";
    55      private static final String TLD_PATTERN = "/^([a-zA-Z]+)$/";
    56
    57      private static final String USER_PATTERN = "/^//s*" + WORD + "(//." + WORD + ")*$/";
    58      private static final String DOMAIN_PATTERN = "/^" + ATOM + "(//." + ATOM + ")*//s*$/";
    59      private static final String ATOM_PATTERN = "/(" + ATOM + ")/";
    60
    61      /***
    62       * Singleton instance of this class.
    63       */
    64      private static final EmailValidator EMAIL_VALIDATOR = new EmailValidator();
    65
    66      /***
    67       * Returns the Singleton instance of this validator.
    68       * @return singleton instance of this validator.
    69       */
    70      public static EmailValidator getInstance() {
    71          return EMAIL_VALIDATOR;
    72      }
    73
    74      /***
    75       * Protected constructor for subclasses to use.
    76       */
    77      protected EmailValidator() {
    78          super();
    79      }
    80
    81      /***
    82       * <p>Checks if a field has a valid e-mail address.</p>
    83       *
    84       * @param email The value validation is being performed on.  A <code>null</code>
    85       * value is considered invalid.
    86       * @return true if the email address is valid.
    87       */
    88      public boolean isValid(String email) {
    89          if (email == null) {
    90              return false;
    91          }
    92
    93          Perl5Util matchAsciiPat = new Perl5Util();
    94          if (!matchAsciiPat.match(LEGAL_ASCII_PATTERN, email)) {
    95              return false;
    96          }
    97
    98          email = stripComments(email);
    99
    100         // Check the whole email address structure
    101         Perl5Util emailMatcher = new Perl5Util();
    102         if (!emailMatcher.match(EMAIL_PATTERN, email)) {
    103             return false;
    104         }
    105
    106         if (email.endsWith(".")) {
    107             return false;
    108         }
    109
    110         if (!isValidUser(emailMatcher.group(1))) {
    111             return false;
    112         }
    113
    114         if (!isValidDomain(emailMatcher.group(2))) {
    115             return false;
    116         }
    117
    118         return true;
    119     }
    120
    121     /***
    122      * Returns true if the domain component of an email address is valid.
    123      * @param domain being validatied.
    124      * @return true if the email address's domain is valid.
    125      */
    126     protected boolean isValidDomain(String domain) {
    127         boolean symbolic = false;
    128         Perl5Util ipAddressMatcher = new Perl5Util();
    129
    130         if (ipAddressMatcher.match(IP_DOMAIN_PATTERN, domain)) {
    131             if (!isValidIpAddress(ipAddressMatcher)) {
    132                 return false;
    133             } else {
    134                 return true;
    135             }
    136         } else {
    137             // Domain is symbolic name
    138             Perl5Util domainMatcher = new Perl5Util();
    139             symbolic = domainMatcher.match(DOMAIN_PATTERN, domain);
    140         }
    141
    142         if (symbolic) {
    143             if (!isValidSymbolicDomain(domain)) {
    144                 return false;
    145             }
    146         } else {
    147             return false;
    148         }
    149
    150         return true;
    151     }
    152
    153     /***
    154      * Returns true if the user component of an email address is valid.
    155      * @param user being validated
    156      * @return true if the user name is valid.
    157      */
    158     protected boolean isValidUser(String user) {
    159         Perl5Util userMatcher = new Perl5Util();
    160         return userMatcher.match(USER_PATTERN, user);
    161     }
    162
    163     /***
    164      * Validates an IP address. Returns true if valid.
    165      * @param ipAddressMatcher Pattren matcher
    166      * @return true if the ip address is valid.
    167      */
    168     protected boolean isValidIpAddress(Perl5Util ipAddressMatcher) {
    169         for (int i = 1; i <= 4; i++) {
    170             String ipSegment = ipAddressMatcher.group(i);
    171             if (ipSegment == null || ipSegment.length() <= 0) {
    172                 return false;
    173             }
    174
    175             int iIpSegment = 0;
    176
    177             try {
    178                 iIpSegment = Integer.parseInt(ipSegment);
    179             } catch(NumberFormatException e) {
    180                 return false;
    181             }
    182
    183             if (iIpSegment > 255) {
    184                 return false;
    185             }
    186
    187         }
    188         return true;
    189     }
    190
    191     /***
    192      * Validates a symbolic domain name.  Returns true if it's valid.
    193      * @param domain symbolic domain name
    194      * @return true if the symbolic domain name is valid.
    195      */
    196     protected boolean isValidSymbolicDomain(String domain) {
    197         String[] domainSegment = new String[10];
    198         boolean match = true;
    199         int i = 0;
    200         Perl5Util atomMatcher = new Perl5Util();
    201         while (match) {
    202             match = atomMatcher.match(ATOM_PATTERN, domain);
    203             if (match) {
    204                 domainSegment[i] = atomMatcher.group(1);
    205                 int l = domainSegment[i].length() + 1;
    206                 domain =
    207                         (l >= domain.length())
    208                         ? ""
    209                         : domain.substring(l);
    210
    211                 i++;
    212             }
    213         }
    214
    215         int len = i;
    216
    217         // Make sure there's a host name preceding the domain.
    218         if (len < 2) {
    219             return false;
    220         }
    221
    222         // TODO: the tld should be checked against some sort of configurable
    223         // list
    224         String tld = domainSegment[len - 1];
    225         if (tld.length() > 1) {
    226             Perl5Util matchTldPat = new Perl5Util();
    227             if (!matchTldPat.match(TLD_PATTERN, tld)) {
    228                 return false;
    229             }
    230         } else {
    231             return false;
    232         }
    233
    234         return true;
    235     }
    236     /***
    237      *   Recursively remove comments, and replace with a single space.  The simpler
    238      *   regexps in the Email Addressing FAQ are imperfect - they will miss escaped
    239      *   chars in atoms, for example.
    240      *   Derived From    Mail::RFC822::Address
    241      * @param emailStr The email address
    242      * @return address with comments removed.
    243     */
    244     protected String stripComments(String emailStr)  {
    245      String input = emailStr;
    246      String result = emailStr;
    247      String commentPat = "s/^((?:[^\"////]|////.)*(?:\"(?:[^\"////]|////.)*\"(?:[^\"////]|\111111////.)*)*)//((?:[^()////]|////.)*//)/$1 /osx";
    248      Perl5Util commentMatcher = new Perl5Util();
    249      result = commentMatcher.substitute(commentPat,input);
    250      // This really needs to be =~ or Perl5Matcher comparison
    251      while (!result.equals(input)) {
    252         input = result;
    253         result = commentMatcher.substitute(commentPat,input);
    254      }
    255      return result;
    256
    257     }
    258 }