Subversion Repositories javautils

Rev

Rev 17 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. package de.viathinksoft.utils.mail.syntaxchecker;
  2.  
  3. import java.util.Arrays;
  4. import java.util.HashSet;
  5. import java.util.regex.Pattern;
  6.  
  7. import de.viathinksoft.utils.mail.EMailAddress;
  8. import de.viathinksoft.utils.mail.InvalidMailAddressException;
  9.  
  10. /**
  11.  *
  12.  * @author Daniel Marschall
  13.  *
  14.  */
  15. public class MailSyntaxChecker {
  16.        
  17.         private static final String REGEX_IP = "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b";
  18.  
  19.         // Führt eine Prüfung der E-Mail-Adresse gemäß SMTP-Spezifikation RFC 5321
  20.         // aus
  21.         private static final boolean CHECK_SMTP_SIZE_LIMITS = false;
  22.  
  23.         // Führt eine Prüfung der TLD gemäß IANA-Daten aus
  24.         private static final boolean CHECK_TLD_RECOGNIZED = true;
  25.  
  26.         // Führt eine DNS-Prüfung durch
  27.         private static final boolean CHECK_DNS = true;
  28.  
  29.         // http://data.iana.org/TLD/tlds-alpha-by-domain.txt
  30.         // Version 2010052500, Last Updated Tue May 25 14:07:02 2010 UTC
  31.         private static final HashSet<String> RECOGNIZED_TLDS_PUNYCODE = hmaker(new String[] {
  32.                         "AC", "AD", "AE", "AERO", "AF", "AG", "AI", "AL", "AM", "AN", "AO",
  33.                         "AQ", "AR", "ARPA", "AS", "ASIA", "AT", "AU", "AW", "AX", "AZ",
  34.                         "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BIZ", "BJ", "BM",
  35.                         "BN", "BO", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CAT",
  36.                         "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO",
  37.                         "COM", "COOP", "CR", "CU", "CV", "CX", "CY", "CZ", "DE", "DJ",
  38.                         "DK", "DM", "DO", "DZ", "EC", "EDU", "EE", "EG", "ER", "ES", "ET",
  39.                         "EU", "FI", "FJ", "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE",
  40.                         "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GOV", "GP", "GQ", "GR",
  41.                         "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU",
  42.                         "ID", "IE", "IL", "IM", "IN", "INFO", "INT", "IO", "IQ", "IR",
  43.                         "IS", "IT", "JE", "JM", "JO", "JOBS", "JP", "KE", "KG", "KH", "KI",
  44.                         "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI",
  45.                         "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME",
  46.                         "MG", "MH", "MIL", "MK", "ML", "MM", "MN", "MO", "MOBI", "MP",
  47.                         "MQ", "MR", "MS", "MT", "MU", "MUSEUM", "MV", "MW", "MX", "MY",
  48.                         "MZ", "NA", "NAME", "NC", "NE", "NET", "NF", "NG", "NI", "NL",
  49.                         "NO", "NP", "NR", "NU", "NZ", "OM", "ORG", "PA", "PE", "PF", "PG",
  50.                         "PH", "PK", "PL", "PM", "PN", "PR", "PRO", "PS", "PT", "PW", "PY",
  51.                         "QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE",
  52.                         "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "ST",
  53.                         "SU", "SV", "SY", "SZ", "TC", "TD", "TEL", "TF", "TG", "TH", "TJ",
  54.                         "TK", "TL", "TM", "TN", "TO", "TP", "TR", "TRAVEL", "TT", "TV",
  55.                         "TW", "TZ", "UA", "UG", "UK", "US", "UY", "UZ", "VA", "VC", "VE",
  56.                         "VG", "VI", "VN", "VU", "WF", "WS", "XN--0ZWM56D",
  57.                         "XN--11B5BS3A9AJ6G", "XN--80AKHBYKNJ4F", "XN--9T4B11YI5A",
  58.                         "XN--DEBA0AD", "XN--G6W251D", "XN--HGBK6AJ7F53BBA",
  59.                         "XN--HLCJ6AYA9ESC7A", "XN--JXALPDLP", "XN--KGBECHTV",
  60.                         "XN--MGBAAM7A8H", "XN--MGBERP4A5D4AR", "XN--P1AI", "XN--WGBH1C",
  61.                         "XN--ZCKZAH", "YE", "YT", "ZA", "ZM", "ZW", });
  62.  
  63.         private static boolean checkSmtpSizeLimits(EMailAddress email) {
  64.                 // RFC 5321: 4.5.3.1.1. Local-part Längenbegrenzung bei SMTP: 64
  65.                 // Byte
  66.                 // QUE: Soll das auch als Punicode-Variante geprüft werden?
  67.                 if ((email.getLocalPart().length() > 64)
  68.                                 || (email.getLocalPart().length() < 1)) {
  69.                         return false;
  70.                 }
  71.  
  72.                 // RFC 5321: 4.5.3.1.2. Domain-part Längenbegrenzung bei SMTP: 255
  73.                 // Byte
  74.                 if ((email.getDomainPartPunycode().length() > 255)
  75.                                 || (email.getDomainPartPunycode().length() < 1)) {
  76.                         return false;
  77.                 }
  78.  
  79.                 // RFC 5321: 4.5.3.1.5. Reply-Line Längenbegrenzung bei SMTP: 512
  80.                 // Byte. Laut
  81.                 // http://de.wikipedia.org/wiki/E-Mail-Adresse#L.C3.A4nge_der_E-Mail-Adresse
  82.                 // folgt daraus: Länge der MailAddresse ist 254 Bytes.
  83.                 if (email.getMailAddressPunycodedDomain().length() > 254) {
  84.                         return false;
  85.                 }
  86.  
  87.                 return true;
  88.         }
  89.  
  90.         private static boolean checkTldRecognized(EMailAddress email) {
  91.                 // TODO: Mailadressen sind aber auch als ...@[IP] gültig. Dann keine
  92.                 // TLD!
  93.                 return RECOGNIZED_TLDS_PUNYCODE.contains(email.getTldPunycode()
  94.                                 .toUpperCase());
  95.         }
  96.  
  97.         private static boolean preg_match(String regex, String data) {
  98.                 return Pattern.compile(regex).matcher(data).matches();
  99.         }
  100.        
  101.         private static boolean checkDns(String domainOrIP) {
  102.                 // TODO
  103.                
  104.                 return true;
  105.         }
  106.  
  107.         public static boolean isMailValid(String email)
  108.                         throws InvalidMailAddressException {
  109.                 return isMailValid(new EMailAddress(email));
  110.         }
  111.  
  112.         /**
  113.          * Checks if an E-Mail-Address is valid
  114.          *
  115.          * @param email
  116.          * @return
  117.          */
  118.         public static boolean isMailValid(EMailAddress email) {
  119.                 if (CHECK_SMTP_SIZE_LIMITS) {
  120.                         if (!checkSmtpSizeLimits(email))
  121.                                 return false;
  122.                 }
  123.  
  124.                 // Begin RFC-Checks
  125.  
  126.                 final String address = email.getMailAddressUnicode();
  127.                 final String localPart = email.getLocalPart();
  128.                 final String domainPart = email.getDomainPartPunycode();
  129.  
  130.                 // Weder localPart noch domainPart dürfen zwei aufeinanderfolgende
  131.                 // Punkte besitzen.
  132.  
  133.                 if (address.contains("..")) {
  134.                         return false;
  135.                 }
  136.  
  137.                 // localPart darf keine Punkte am Anfang oder Ende besitzen
  138.                 if (localPart.length() == 0) return false;
  139.                 String lpFirstChar = localPart.substring(0, 1);
  140.                 String lpLastChar = localPart.substring(localPart.length()-1);
  141.                 if (lpFirstChar.equals(".") || (lpLastChar.equals("."))) {
  142.                         return false;
  143.                 }
  144.  
  145.                 // domainPart darf keine Punkte am Anfang oder Ende besitzen
  146.  
  147.                 String dpFirstChar = domainPart.substring(0, 1);
  148.                 String dpLastChar = domainPart.substring(domainPart.length()-1);
  149.  
  150.                 if (dpFirstChar.equals(".") || (dpLastChar.equals("."))) {
  151.                         return false;
  152.                 }
  153.  
  154.                 // domainPart prüfen
  155.                
  156.                 if (preg_match("^"+REGEX_IP+"$", domainPart)) {
  157.                         // domainPart is <IP>
  158.                         // QUE: Ist das überhaupt gemäß RFC gültig?
  159.                        
  160.                         String ip = ""; // TODO
  161.                        
  162.                         if (CHECK_DNS) {
  163.                                 if (!checkDns(ip)) return false;
  164.                         }
  165.                 } else if (preg_match("^\\["+REGEX_IP+"\\]$", domainPart)) {
  166.                         // domainPart is [<IP>]
  167.                        
  168.                         String ip = ""; // TODO
  169.                        
  170.                         if (CHECK_DNS) {
  171.                                 if (!checkDns(ip)) return false;
  172.                         }
  173.                 } else {
  174.                         if (!preg_match("^[A-Za-z0-9\\-\\.]+$", domainPart)) {
  175.                                 return false;
  176.                         }
  177.  
  178.                         if (CHECK_TLD_RECOGNIZED) {
  179.                                 if (!checkTldRecognized(email))
  180.                                         return false;
  181.                         }
  182.                        
  183.                         if (CHECK_DNS) {
  184.                                 if (!checkDns(domainPart)) return false;
  185.                         }
  186.                 }
  187.                
  188.                 // localPart prüfen
  189.                
  190.                 if (!preg_match("^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$",
  191.                                 localPart.replaceAll("\\\\", "").replaceAll("@", "") )) {
  192.                         // character not valid in local part unless
  193.                         // local part is quoted
  194.                         if (!preg_match("^\"(\\\\\"|[^\"])+\"$",
  195.                                           localPart.replaceAll("\\\\", "").replaceAll("@", "") )) {
  196.                                 return false;
  197.                         }
  198.                 }
  199.                
  200.                 // TODO: Weitere Tests gemäß RFC?
  201.                
  202.                 return true;
  203.         }
  204.  
  205.         /**
  206.          * build a HashSet from a array of String literals.
  207.          *
  208.          * @param list
  209.          *            array of strings
  210.          *
  211.          * @return HashSet you can use to test if a string is in the set.
  212.          */
  213.         private static HashSet<String> hmaker(String[] list) {
  214.                 HashSet<String> map = new HashSet<String>(Math.max(
  215.                                 (int) (list.length / .75f) + 1, 16));
  216.                 map.addAll(Arrays.asList(list));
  217.                 return map;
  218.         }
  219. }
  220.