Subversion Repositories javautils

Rev

Rev 3 | Rev 17 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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