From 545b5ea78f0b733a805cf82e3ae27edfb3573e5f Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Thu, 27 Mar 2025 15:35:25 -0400 Subject: [PATCH 1/3] Add isEmail validation --- conformance/expected-failures.yaml | 40 ------------------- .../buf/protovalidate/CustomOverload.java | 33 +++++++-------- 2 files changed, 15 insertions(+), 58 deletions(-) diff --git a/conformance/expected-failures.yaml b/conformance/expected-failures.yaml index be9c0ac3d..32e5f20b5 100644 --- a/conformance/expected-failures.yaml +++ b/conformance/expected-failures.yaml @@ -107,46 +107,6 @@ custom_constraints: #ERROR: :1:1: expression of type 'int' cannot be range of a comprehension (must be list, map, or dynamic) # | this.all(e, e == 1) # | ^ -library/is_email: - - invalid/non_ascii - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"ยต@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # got: valid - - invalid/quoted-string/a - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"\"foo bar\"@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # got: valid - - invalid/quoted-string/b - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"\"foo..bar\"@example.com"} - # want: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # got: valid - - valid/empty_atext - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:".@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # message: "" - - valid/exhaust_atext - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'*+-/=?^_`{|}~@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # message: "" - - valid/label_all_digits - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"foo@0.1.2.3.4.5.6.7.8.9"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # message: "" - - valid/multiple_empty_atext - # input: [type.googleapis.com/buf.validate.conformance.cases.IsEmail]:{val:"...@example.com"} - # want: valid - # got: validation error (1 violation) - # 1. constraint_id: "library.is_email" - # message: "" library/is_host_and_port: - port_required/false/invalid/ipv6_zone-id_too_short # input: [type.googleapis.com/buf.validate.conformance.cases.IsHostAndPort]:{val:"[::1%]"} diff --git a/src/main/java/build/buf/protovalidate/CustomOverload.java b/src/main/java/build/buf/protovalidate/CustomOverload.java index cd60e93f3..7b27688d7 100644 --- a/src/main/java/build/buf/protovalidate/CustomOverload.java +++ b/src/main/java/build/buf/protovalidate/CustomOverload.java @@ -20,8 +20,6 @@ import com.google.common.primitives.Bytes; import inet.ipaddr.IPAddress; import inet.ipaddr.IPAddressString; -import jakarta.mail.internet.AddressException; -import jakarta.mail.internet.InternetAddress; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -29,6 +27,8 @@ import java.net.URISyntaxException; import java.util.HashSet; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.projectnessie.cel.common.types.BoolT; import org.projectnessie.cel.common.types.Err; import org.projectnessie.cel.common.types.IntT; @@ -58,6 +58,11 @@ final class CustomOverload { private static final String OVERLOAD_IS_INF = "isInf"; private static final String OVERLOAD_IS_HOST_AND_PORT = "isHostAndPort"; + // See https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address + private static final Pattern EMAIL_REGEX = + Pattern.compile( + "^[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])?)*$"); + /** * Create custom function overload list. * @@ -514,27 +519,19 @@ private static Val uniqueList(Lister list) { } /** - * Validates if the input string is a valid email address. + * validateEmail returns true if addr is a valid email address. + * + *

This regex conforms to the definition for a valid email address from the HTML standard. Note + * that this standard willfully deviates from RFC 5322, which allows many unexpected forms of + * email addresses and will easily match a typographical error. * * @param addr The input string to validate as an email address. * @return {@code true} if the input string is a valid email address, {@code false} otherwise. */ private static boolean validateEmail(String addr) { - try { - InternetAddress emailAddr = new InternetAddress(addr); - emailAddr.validate(); - if (addr.contains("<") || !emailAddr.getAddress().equals(addr)) { - return false; - } - addr = emailAddr.getAddress(); - if (addr.length() > 254) { - return false; - } - String[] parts = addr.split("@", 2); - return parts[0].length() < 64 && validateHostname(parts[1]); - } catch (AddressException ex) { - return false; - } + Matcher m = EMAIL_REGEX.matcher(addr); + + return m.matches(); } /** From ea269b43d2ec2988a0d16b782dd0de3aeac66df7 Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Thu, 27 Mar 2025 15:38:58 -0400 Subject: [PATCH 2/3] Add isEmail validation --- src/main/java/build/buf/protovalidate/CustomOverload.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/build/buf/protovalidate/CustomOverload.java b/src/main/java/build/buf/protovalidate/CustomOverload.java index 7b27688d7..236df97cb 100644 --- a/src/main/java/build/buf/protovalidate/CustomOverload.java +++ b/src/main/java/build/buf/protovalidate/CustomOverload.java @@ -27,7 +27,6 @@ import java.net.URISyntaxException; import java.util.HashSet; import java.util.Set; -import java.util.regex.Matcher; import java.util.regex.Pattern; import org.projectnessie.cel.common.types.BoolT; import org.projectnessie.cel.common.types.Err; @@ -529,9 +528,7 @@ private static Val uniqueList(Lister list) { * @return {@code true} if the input string is a valid email address, {@code false} otherwise. */ private static boolean validateEmail(String addr) { - Matcher m = EMAIL_REGEX.matcher(addr); - - return m.matches(); + return EMAIL_REGEX.matcher(addr).matches(); } /** From 301990d4408cd1ac632cc97f231ec3ed78bed309 Mon Sep 17 00:00:00 2001 From: Steve Ayers Date: Fri, 28 Mar 2025 10:46:30 -0400 Subject: [PATCH 3/3] Remove jakarta mail dep --- build.gradle.kts | 1 - gradle/libs.versions.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f5b11d685..b53fb0c77 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -272,7 +272,6 @@ dependencies { implementation(libs.cel.core) implementation(libs.guava) implementation(libs.ipaddress) - implementation(libs.jakarta.mail.api) buf("build.buf:buf:${libs.versions.buf.get()}:${osdetector.classifier}@exe") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e1a94544..382e507bd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,6 @@ cel-core = { module = "org.projectnessie.cel:cel-core" } errorprone = { module = "com.google.errorprone:error_prone_core", version = "2.37.0" } guava = { module = "com.google.guava:guava", version = "33.4.0-jre" } ipaddress = { module = "com.github.seancfoley:ipaddress", version.ref = "ipaddress" } -jakarta-mail-api = { module = "jakarta.mail:jakarta.mail-api", version = "2.1.3" } junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" } maven-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven-publish" } nullaway = { module = "com.uber.nullaway:nullaway", version = "0.12.4" }