use regex::Regex;
use std::sync::LazyLock;
macro_rules! static_regex {
($name:ident, $pattern:expr) => {
static $name: LazyLock<Regex> = LazyLock::new(|| Regex::new($pattern).unwrap());
};
}
static_regex!(
EMAIL_RE,
r#"(?i)^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$"#
);
static_regex!(LANG_RE, r#"^[a-z]{2}(-[A-Z]{2})?$"#);
static_regex!(
URL_RE,
r#"^(https?://)([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])?)+)(:[0-9]{1,5})?(/[^\s?#]*)?(\?[^\s#]*)?(\#[^\s]*)?$"#
);
static_regex!(NAME_RE, r#"^[\p{L}\p{M}\p{N} ._''\-]{2,128}$"#);
pub fn is_valid_password(
password: &str,
min_length: usize,
require_upper: bool,
require_lower: bool,
require_digit: bool,
require_special: bool,
) -> bool {
if password.len() < min_length {
return false;
}
if require_upper && !password.chars().any(|c| c.is_ascii_uppercase()) {
return false;
}
if require_lower && !password.chars().any(|c| c.is_ascii_lowercase()) {
return false;
}
if require_digit && !password.chars().any(|c| c.is_ascii_digit()) {
return false;
}
if require_special && !password.chars().any(|c| !c.is_alphanumeric()) {
return false;
}
true
}
pub fn is_valid_email(email: &str) -> bool {
if email.len() > 254 {
return false;
}
EMAIL_RE.is_match(email)
}
pub fn is_valid_language_code(code: &str) -> bool {
if code.len() != 2 && code.len() != 5 {
return false;
}
LANG_RE.is_match(code)
}
pub fn is_valid_country_code(code: &str) -> bool {
let len = code.len();
(len == 2 || len == 3) && code.chars().all(|c| c.is_ascii_uppercase())
}
pub fn is_valid_url_format(url: &str) -> bool {
if url.len() > 2048 {
return false;
}
URL_RE.is_match(url.trim())
}
pub fn is_valid_name(name: &str) -> bool {
NAME_RE.is_match(name)
}
pub fn is_valid_otp_code(code: &str, length: u8) -> bool {
code.len() == length as usize && code.chars().all(|c| c.is_ascii_digit())
}
pub const MAX_PASSWORD_BYTES: usize = 1024;
pub const MAX_OTP_CODE_BYTES: usize = 64;
pub const MAX_IDENTIFIER_BYTES: usize = 256;
pub const MAX_DISPLAY_NAME_BYTES: usize = 256;
pub const MAX_OAUTH_PARAM_BYTES: usize = 4096;
pub fn is_printable(s: &str) -> bool {
s.chars()
.all(|c| c == ' ' || (!c.is_control() && c != '\u{FEFF}'))
}
pub fn compare_secrets(a: &[u8], b: &[u8]) -> bool {
use subtle::ConstantTimeEq;
a.ct_eq(b).into()
}
#[cfg(test)]
mod validation_tests {
use super::*;
#[test]
fn password_length_boundary_strict_less_than() {
assert!(!is_valid_password("ab", 3, false, false, false, false));
assert!(is_valid_password("abc", 3, false, false, false, false));
assert!(is_valid_password("abcd", 3, false, false, false, false));
assert!(!is_valid_password("", 1, false, false, false, false));
}
#[test]
fn password_meeting_all_requirements_returns_true() {
assert!(is_valid_password("Aa1!aaaa", 8, true, true, true, true));
}
#[test]
fn password_missing_any_required_class_rejects() {
assert!(!is_valid_password("aaaa1!aa", 4, true, false, false, false));
assert!(!is_valid_password("AAAA1!AA", 4, false, true, false, false));
assert!(!is_valid_password("Aaaa!aaa", 4, false, false, true, false));
assert!(!is_valid_password("Aaaa1aaa", 4, false, false, false, true));
}
#[test]
fn password_with_no_requirements_just_length_passes() {
assert!(is_valid_password(
"aaaaaaaaaa",
5,
false,
false,
false,
false
));
}
#[test]
fn email_length_boundary_at_254() {
assert!(is_valid_email("alice@example.com"));
let at_254 = format!(
"abc@{}.{}.{}.{}",
"b".repeat(63),
"c".repeat(63),
"d".repeat(63),
"e".repeat(58)
);
assert_eq!(at_254.len(), 254);
assert!(
is_valid_email(&at_254),
"email of exactly 254 bytes must pass the length guard \
(kills `> with ==` and `> with >=` mutations)"
);
let at_255 = format!("a@{}.co", "b".repeat(250));
assert_eq!(at_255.len(), 255);
assert!(
!is_valid_email(&at_255),
"email of 255 bytes must be rejected by the > 254 length guard"
);
}
#[test]
fn email_obvious_garbage_rejected() {
assert!(!is_valid_email("not-an-email"));
assert!(!is_valid_email("missing-at.example.com"));
assert!(!is_valid_email("@no-local.com"));
}
#[test]
fn language_code_length_boundaries() {
assert!(is_valid_language_code("en"));
assert!(is_valid_language_code("en-US"));
assert!(!is_valid_language_code("eng"));
assert!(!is_valid_language_code("enUS"));
assert!(!is_valid_language_code("en-USA"));
assert!(!is_valid_language_code("e"));
}
#[test]
fn language_code_wrong_shape_rejected() {
assert!(!is_valid_language_code("EN")); assert!(!is_valid_language_code("en-us")); assert!(!is_valid_language_code("e1"));
}
#[test]
fn country_code_length_branches() {
assert!(is_valid_country_code("US"));
assert!(is_valid_country_code("CHE"));
assert!(!is_valid_country_code("U"));
assert!(!is_valid_country_code("USAA"));
}
#[test]
fn country_code_lowercase_rejected() {
assert!(!is_valid_country_code("us"));
assert!(!is_valid_country_code("usa"));
assert!(!is_valid_country_code("Us"));
}
#[test]
fn url_length_boundary_at_2048() {
assert!(is_valid_url_format("https://example.com/path?x=1"));
let at_2048 = format!("https://a.co/{}", "a".repeat(2048 - 13));
assert_eq!(at_2048.len(), 2048);
assert!(
is_valid_url_format(&at_2048),
"URL of exactly 2048 bytes must pass the length guard \
(kills `> with ==` and `> with >=` mutations)"
);
let at_2049 = format!("https://a.co/{}", "a".repeat(2049 - 13));
assert_eq!(at_2049.len(), 2049);
assert!(
!is_valid_url_format(&at_2049),
"URL of 2049 bytes must be rejected by the > 2048 length guard"
);
}
#[test]
fn url_obvious_garbage_rejected() {
assert!(!is_valid_url_format("not a url"));
assert!(!is_valid_url_format("ftp://example.com")); assert!(!is_valid_url_format("https://"));
}
#[test]
fn name_positive_and_negative_cases() {
assert!(is_valid_name("Alice Liddell"));
assert!(is_valid_name("Mc'Donald"));
assert!(!is_valid_name("a")); assert!(!is_valid_name("A".repeat(129).as_str())); assert!(!is_valid_name("Alice<script>")); }
#[test]
fn otp_length_and_digit_only() {
assert!(is_valid_otp_code("123456", 6));
assert!(!is_valid_otp_code("12345", 6));
assert!(!is_valid_otp_code("1234567", 6));
assert!(!is_valid_otp_code("12345a", 6));
assert!(!is_valid_otp_code("12345 ", 6));
assert!(is_valid_otp_code("", 0));
}
#[test]
fn compare_secrets_equal_and_unequal() {
let a: &[u8] = b"some-secret-value";
let b: &[u8] = b"some-secret-value";
let c: &[u8] = b"other-secret-val!";
assert!(compare_secrets(a, b));
assert!(!compare_secrets(a, c));
}
#[test]
fn compare_secrets_length_mismatch_is_false() {
assert!(!compare_secrets(b"short", b"shorter"));
assert!(!compare_secrets(b"", b"non-empty"));
}
#[test]
fn compare_secrets_empty_pair_is_true() {
assert!(compare_secrets(b"", b""));
}
#[test]
fn is_printable_distinguishes_printable_and_control_chars() {
assert!(is_printable("hello world"));
assert!(is_printable("café"));
assert!(is_printable(" "));
assert!(is_printable(""));
assert!(!is_printable("a\0b"));
assert!(!is_printable("a\tb"));
assert!(!is_printable("line\n"));
assert!(!is_printable("\u{7F}"));
assert!(!is_printable("\u{FEFF}"));
assert!(
!is_printable("text\u{FEFF}with-bom"),
"an embedded BOM must reject (kills `!= → ==` on BOM)"
);
}
}