use std::net::IpAddr; pub(crate) struct ParsedList { pub(crate) items: Vec, pub(crate) invalid: bool, pub(crate) out_of_range: bool, } pub(crate) fn valid_percent_encoding(value: &str) -> bool { let bytes = value.as_bytes(); let mut index = 0; while index < bytes.len() { if bytes[index] == b'%' { if index + 2 >= bytes.len() || !bytes[index + 1].is_ascii_hexdigit() || !bytes[index + 2].is_ascii_hexdigit() { return false; } index += 3; } else { index += 1; } } true } pub(crate) fn valid_database_host(value: &str) -> bool { !value.is_empty() && value.len() <= 253 && !value.chars().any(char::is_whitespace) && (value.parse::().is_ok() || value.split('.').all(|label| { !label.is_empty() && label.len() <= 63 && label .bytes() .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-') && !label.starts_with('-') && !label.ends_with('-') })) } pub(crate) fn valid_database_identifier(value: &str) -> bool { !value.is_empty() && value.len() <= 128 && value .bytes() .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.')) } pub(crate) fn parse_host_list(raw: &str) -> ParsedList { let mut result = ParsedList { items: Vec::new(), invalid: false, out_of_range: raw.len() > 16_384, }; if result.out_of_range { return result; } for item in raw.split(',') { let item = item.trim().to_ascii_lowercase(); let base = item.strip_prefix("*.").unwrap_or(&item); let valid_ip = !item.starts_with("*.") && base.parse::().is_ok(); if item.is_empty() || item.len() > 253 || item.contains('/') || item.contains(char::is_whitespace) || base.is_empty() || (!valid_ip && base.contains(':')) || (item.starts_with("*.") && base.parse::().is_ok()) { result.invalid = true; continue; } if !result.items.contains(&item) { result.items.push(item); } } if result.items.len() > 256 { result.out_of_range = true; result.items.truncate(256); } result } pub(crate) fn parse_ip_list(raw: &str) -> ParsedList { let mut result = ParsedList { items: Vec::new(), invalid: false, out_of_range: raw.len() > 4096, }; if result.out_of_range { return result; } for item in raw.split(',').map(str::trim) { match item.parse::() { Ok(value) if !result.items.contains(&value) => result.items.push(value), Ok(_) => {} Err(_) => result.invalid = true, } } if result.items.is_empty() { result.invalid = true; } if result.items.len() > 64 { result.out_of_range = true; result.items.truncate(64); } result }