From 608b1fee8040ceec860b3e2cec882176fc9de47d Mon Sep 17 00:00:00 2001 From: lucasBRT Date: Sat, 3 May 2025 14:52:06 -0300 Subject: [PATCH] fix(email): reject domains without a dot (e.g. user@com) --- validator/src/validation/email.rs | 29 +++++++++++++++++---------- validator_derive_tests/tests/email.rs | 21 +++++++++++++++++++ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/validator/src/validation/email.rs b/validator/src/validation/email.rs index 7e71caf9..df1f569c 100644 --- a/validator/src/validation/email.rs +++ b/validator/src/validation/email.rs @@ -19,20 +19,21 @@ static EMAIL_LITERAL_RE: LazyLock = LazyLock::new(|| Regex::new(r"\[([a-fA-F0-9:\.]+)\]\z").unwrap()); /// Checks if the domain is a valid domain and if not, check whether it's an IP -#[must_use] fn validate_domain_part(domain_part: &str) -> bool { - if EMAIL_DOMAIN_RE.is_match(domain_part) { - return true; - } - - // maybe we have an ip as a domain? - match EMAIL_LITERAL_RE.captures(domain_part) { - Some(caps) => match caps.get(1) { + // if it's a domain literal like [127.0.0.1] + if let Some(caps) = EMAIL_LITERAL_RE.captures(domain_part) { + return match caps.get(1) { Some(c) => c.as_str().validate_ip(), None => false, - }, - None => false, + }; } + + // Check if domain matches pattern and contains at least one dot + if EMAIL_DOMAIN_RE.is_match(domain_part) && domain_part.contains('.') { + return true; + } + + false } /// Validates whether the given string is an email based on the [HTML5 spec](https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address). @@ -154,7 +155,7 @@ mod tests { ("", false), ("abc", false), ("abc@", false), - ("abc@bar", true), + ("abc@bar", false), ("a @x.cz", false), ("abc@.com", false), ("something@@somewhere.com", false), @@ -213,4 +214,10 @@ mod tests { let test = "a@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com"; assert!(!test.validate_email()); } + + #[test] + fn test_user_at_com_fails() { + let test = "user@com"; + assert!(!test.validate_email(), "user@com should not be valid"); + } } diff --git a/validator_derive_tests/tests/email.rs b/validator_derive_tests/tests/email.rs index 7af76851..03ec2e68 100644 --- a/validator_derive_tests/tests/email.rs +++ b/validator_derive_tests/tests/email.rs @@ -100,3 +100,24 @@ fn can_validate_custom_impl_for_email() { assert!(valid.validate().is_ok()); assert!(invalid.validate().is_err()); } + +#[test] +fn top_level_domain_only_fails_validation() { + #[derive(Debug, Validate)] + struct TestStruct { + #[validate(email)] + val: String, + } + + let s = TestStruct { val: "user@com".to_string() }; + let res = s.validate(); + assert!(res.is_err()); + + let err = res.unwrap_err(); + let errs = err.field_errors(); + + assert!(errs.contains_key("val")); + assert_eq!(errs["val"].len(), 1); + assert_eq!(errs["val"][0].code, "email"); + assert_eq!(errs["val"][0].params["value"], "user@com"); +}