Skip to content

Commit b15c10c

Browse files
committed
improve EmailValidator
1 parent f2fe13d commit b15c10c

1 file changed

Lines changed: 59 additions & 72 deletions

File tree

desktop/src/main/java/bisq/desktop/util/validation/EmailValidator.java

Lines changed: 59 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -20,91 +20,78 @@
2020
import bisq.core.locale.Res;
2121
import bisq.core.util.validation.InputValidator;
2222

23+
import java.util.regex.Matcher;
24+
import java.util.regex.Pattern;
25+
2326
/*
24-
* Mail addresses consist of localPart @ domainPart
27+
* Email addresses consist of localPart @ domainPart
2528
*
2629
* Local part:
27-
* May contain lots of symbols A-Za-z0-9.!#$%&'*+-/=?^_`{|}~
28-
* but cannot begin or end with a dot (.)
29-
* between double quotes many more symbols are allowed:
30-
* "(),:;<>@[\] (ASCII: 32, 34, 40, 41, 44, 58, 59, 60, 62, 64, 91–93)
30+
* - Practical subset allowed: A-Za-z0-9._%+- (max 64 characters)
31+
* - Leading, trailing, and consecutive dots are not allowed
32+
* - Quoted strings and exotic RFC constructs are intentionally excluded
3133
*
3234
* Domain part:
33-
* Consists of name dot TLD
34-
* name can but usually doesn't (compatibility reasons) contain non-ASCII
35-
* symbols.
36-
* TLD is at least two letters long
35+
* - Labels separated by dots, each 1..63 characters
36+
* - Each label starts and ends with alphanumeric character
37+
* - Hyphens allowed internally
38+
* - At least one dot required
39+
* - Last label (TLD) must be at least 2 characters, letters-only (or punycode starting with "xn--")
40+
*
41+
* Conservative validator:
42+
* - Accepts common, interoperable email addresses
43+
* - Rejects edge-case RFC addresses that break UIs, JSON, or payment rails
3744
*/
3845
public final class EmailValidator extends InputValidator {
3946

40-
///////////////////////////////////////////////////////////////////////////////////////////
41-
// Public methods
42-
///////////////////////////////////////////////////////////////////////////////////////////
47+
// Precompiled regex pattern for efficient validation
48+
// Local part: labels separated by single dots (prevents leading/trailing/consecutive dots)
49+
// Domain part: labels with internal hyphens only, at least one dot
50+
private static final Pattern SIMPLE_EMAIL_PATTERN = Pattern.compile(
51+
"^[A-Za-z0-9_%+\\-]+(?:\\.[A-Za-z0-9_%+\\-]+)*@" + // local-part
52+
"[A-Za-z0-9](?:[A-Za-z0-9\\-]{0,61}[A-Za-z0-9])?" + // first domain label
53+
"(?:\\.[A-Za-z0-9](?:[A-Za-z0-9\\-]{0,61}[A-Za-z0-9])?)+$" // additional labels
54+
);
4355

44-
private final ValidationResult invalidAddress = new ValidationResult(false, Res.get("validation.email.invalidAddress"));
56+
private static final ValidationResult INVALID_ADDRESS = new ValidationResult(false, Res.get("validation.email.invalidAddress"));
4557

4658
@Override
4759
public ValidationResult validate(String input) {
48-
if (input == null || input.length() < 6 || input.length() > 100) // shortest address is l@d.cc, max length 100
49-
return invalidAddress;
50-
String[] subStrings;
51-
String local, domain;
52-
53-
subStrings = input.split("@", -1);
54-
55-
if (subStrings.length == 1) // address does not contain '@'
56-
return invalidAddress;
57-
if (subStrings.length > 2) // multiple @'s included -> check for valid double quotes
58-
if (!checkForValidQuotes(subStrings)) // around @'s -> "..@..@.." and concatenate local part
59-
return invalidAddress;
60-
local = subStrings[0];
61-
domain = subStrings[subStrings.length - 1];
62-
63-
if (local.isEmpty())
64-
return invalidAddress;
65-
66-
// local part cannot begin or end with '.'
67-
if (local.startsWith(".") || local.endsWith("."))
68-
return invalidAddress;
69-
70-
String[] splitDomain = domain.split("\\.", -1); // '.' is a regex in java and has to be escaped
71-
String tld = splitDomain[splitDomain.length - 1];
72-
if (splitDomain.length < 2)
73-
return invalidAddress;
74-
75-
if (splitDomain[0] == null || splitDomain[0].isEmpty())
76-
return invalidAddress;
77-
78-
// TLD length is at least two
79-
if (tld.length() < 2)
80-
return invalidAddress;
81-
82-
// TLD is letters only
83-
for (int k = 0; k < tld.length(); k++)
84-
if (!Character.isLetter(tld.charAt(k)))
85-
return invalidAddress;
86-
return new ValidationResult(true);
87-
}
88-
89-
///////////////////////////////////////////////////////////////////////////////////////////
90-
// Private methods
91-
///////////////////////////////////////////////////////////////////////////////////////////
92-
93-
private boolean checkForValidQuotes(String[] subStrings) {
94-
int length = subStrings.length - 2; // is index on last substring of local part
95-
96-
// check for odd number of double quotes before first and after last '@'
97-
if ((subStrings[0].split("\"", -1).length % 2 == 1) || (subStrings[length].split("\"", -1).length % 2 == 1))
98-
return false;
99-
for (int k = 1; k < length; k++) {
100-
if (subStrings[k].split("\"", -1).length % 2 == 0)
101-
return false;
60+
if (input == null) return INVALID_ADDRESS;
61+
62+
String email = input.trim();
63+
64+
// Practical length limits: shortest plausible a@b.cc (6), longest allowed by spec 254
65+
if (email.length() < 6 || email.length() > 254) return INVALID_ADDRESS;
66+
67+
// Apply regex pattern
68+
Matcher matcher = SIMPLE_EMAIL_PATTERN.matcher(email);
69+
if (!matcher.matches()) return INVALID_ADDRESS;
70+
71+
// Check TLD (last label after final dot)
72+
int lastDot = email.lastIndexOf('.');
73+
if (lastDot < 0 || lastDot == email.length() - 1) return INVALID_ADDRESS;
74+
75+
String tld = email.substring(lastDot + 1);
76+
77+
// TLD length must be at least 2 characters
78+
if (tld.length() < 2) return INVALID_ADDRESS;
79+
80+
// allow punycode TLDs for IDN support
81+
if (tld.startsWith("xn--")) {
82+
// basic sanity check for punycode TLD
83+
if (tld.length() > 63) return INVALID_ADDRESS;
84+
for (int i = 4; i < tld.length(); i++) {
85+
char c = tld.charAt(i);
86+
if (!(Character.isLetterOrDigit(c) || c == '-')) return INVALID_ADDRESS;
87+
}
88+
} else {
89+
// ASCII letters only
90+
for (int i = 0; i < tld.length(); i++) {
91+
if (!Character.isLetter(tld.charAt(i))) return INVALID_ADDRESS;
92+
}
10293
}
10394

104-
String patchLocal = "";
105-
for (int k = 0; k <= length; k++) // remember: length is last index not array length
106-
patchLocal = patchLocal.concat(subStrings[k]); // @'s are not reinstalled, since not needed for further checks
107-
subStrings[0] = patchLocal;
108-
return true;
95+
return new ValidationResult(true);
10996
}
11097
}

0 commit comments

Comments
 (0)