Skip to content

Commit d4dcad8

Browse files
authored
Enhance isEmail validation (#250)
This enhances the current isEmail validation by using a consistent regex that will be used across protovalidate implementations.
1 parent bc9a433 commit d4dcad8

File tree

4 files changed

+12
-60
lines changed

4 files changed

+12
-60
lines changed

build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,6 @@ dependencies {
272272
implementation(libs.cel.core)
273273
implementation(libs.guava)
274274
implementation(libs.ipaddress)
275-
implementation(libs.jakarta.mail.api)
276275

277276
buf("build.buf:buf:${libs.versions.buf.get()}:${osdetector.classifier}@exe")
278277

conformance/expected-failures.yaml

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -107,46 +107,6 @@ custom_constraints:
107107
#ERROR: <input>:1:1: expression of type 'int' cannot be range of a comprehension (must be list, map, or dynamic)
108108
# | this.all(e, e == 1)
109109
# | ^
110-
library/is_email:
111-
- invalid/non_ascii
112-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"µ@example.com"}
113-
# want: validation error (1 violation)
114-
# 1. constraint_id: "library.is_email"
115-
# got: valid
116-
- invalid/quoted-string/a
117-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"\"foo bar\"@example.com"}
118-
# want: validation error (1 violation)
119-
# 1. constraint_id: "library.is_email"
120-
# got: valid
121-
- invalid/quoted-string/b
122-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"\"foo..bar\"@example.com"}
123-
# want: validation error (1 violation)
124-
# 1. constraint_id: "library.is_email"
125-
# got: valid
126-
- valid/empty_atext
127-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"[email protected]"}
128-
# want: valid
129-
# got: validation error (1 violation)
130-
# 1. constraint_id: "library.is_email"
131-
# message: ""
132-
- valid/exhaust_atext
133-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'*+-/=?^_`{|}[email protected]"}
134-
# want: valid
135-
# got: validation error (1 violation)
136-
# 1. constraint_id: "library.is_email"
137-
# message: ""
138-
- valid/label_all_digits
139-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"[email protected]"}
140-
# want: valid
141-
# got: validation error (1 violation)
142-
# 1. constraint_id: "library.is_email"
143-
# message: ""
144-
- valid/multiple_empty_atext
145-
# input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"[email protected]"}
146-
# want: valid
147-
# got: validation error (1 violation)
148-
# 1. constraint_id: "library.is_email"
149-
# message: ""
150110
library/is_host_and_port:
151111
- port_required/false/invalid/ipv6_zone-id_too_short
152112
# input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"[::1%]"}

gradle/libs.versions.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ cel-core = { module = "org.projectnessie.cel:cel-core" }
2020
errorprone = { module = "com.google.errorprone:error_prone_core", version = "2.37.0" }
2121
guava = { module = "com.google.guava:guava", version = "33.4.0-jre" }
2222
ipaddress = { module = "com.github.seancfoley:ipaddress", version.ref = "ipaddress" }
23-
jakarta-mail-api = { module = "jakarta.mail:jakarta.mail-api", version = "2.1.3" }
2423
junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
2524
maven-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven-publish" }
2625
nullaway = { module = "com.uber.nullaway:nullaway", version = "0.12.4" }

src/main/java/build/buf/protovalidate/CustomOverload.java

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@
2020
import com.google.common.primitives.Bytes;
2121
import inet.ipaddr.IPAddress;
2222
import inet.ipaddr.IPAddressString;
23-
import jakarta.mail.internet.AddressException;
24-
import jakarta.mail.internet.InternetAddress;
2523
import java.net.Inet4Address;
2624
import java.net.Inet6Address;
2725
import java.net.InetAddress;
2826
import java.net.URI;
2927
import java.net.URISyntaxException;
3028
import java.util.HashSet;
3129
import java.util.Set;
30+
import java.util.regex.Pattern;
3231
import org.projectnessie.cel.common.types.BoolT;
3332
import org.projectnessie.cel.common.types.Err;
3433
import org.projectnessie.cel.common.types.IntT;
@@ -58,6 +57,11 @@ final class CustomOverload {
5857
private static final String OVERLOAD_IS_INF = "isInf";
5958
private static final String OVERLOAD_IS_HOST_AND_PORT = "isHostAndPort";
6059

60+
// See https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
61+
private static final Pattern EMAIL_REGEX =
62+
Pattern.compile(
63+
"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");
64+
6165
/**
6266
* Create custom function overload list.
6367
*
@@ -514,27 +518,17 @@ private static Val uniqueList(Lister list) {
514518
}
515519

516520
/**
517-
* Validates if the input string is a valid email address.
521+
* validateEmail returns true if addr is a valid email address.
522+
*
523+
* <p>This regex conforms to the definition for a valid email address from the HTML standard. Note
524+
* that this standard willfully deviates from RFC 5322, which allows many unexpected forms of
525+
* email addresses and will easily match a typographical error.
518526
*
519527
* @param addr The input string to validate as an email address.
520528
* @return {@code true} if the input string is a valid email address, {@code false} otherwise.
521529
*/
522530
private static boolean validateEmail(String addr) {
523-
try {
524-
InternetAddress emailAddr = new InternetAddress(addr);
525-
emailAddr.validate();
526-
if (addr.contains("<") || !emailAddr.getAddress().equals(addr)) {
527-
return false;
528-
}
529-
addr = emailAddr.getAddress();
530-
if (addr.length() > 254) {
531-
return false;
532-
}
533-
String[] parts = addr.split("@", 2);
534-
return parts[0].length() < 64 && validateHostname(parts[1]);
535-
} catch (AddressException ex) {
536-
return false;
537-
}
531+
return EMAIL_REGEX.matcher(addr).matches();
538532
}
539533

540534
/**

0 commit comments

Comments
 (0)