use super::{validate_length, validate_not_empty};
use crate::constants::validation;
use crate::errors::prelude::{CliError, Result as CliResult};
pub fn validate_message_text(message: &str) -> CliResult<()> {
validate_not_empty(message, "Message")?;
validate_length(
message,
"Message",
validation::MIN_MESSAGE_LENGTH,
validation::MAX_MESSAGE_LENGTH,
)?;
if message.contains('\0') {
return Err(CliError::InputError(
"Message cannot contain null bytes".to_string(),
));
}
Ok(())
}
pub fn validate_message_id(message_id: &str) -> CliResult<()> {
validate_not_empty(message_id, "Message ID")?;
if !message_id
.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
{
return Err(CliError::InputError(
"Message ID contains invalid characters. Only alphanumeric, hyphen, and underscore are allowed".to_string()
));
}
Ok(())
}
pub fn validate_message_formatting(message: &str) -> CliResult<()> {
let max_line_length = 1000;
for (line_num, line) in message.lines().enumerate() {
if line.len() > max_line_length {
return Err(CliError::InputError(format!(
"Line {} is too long ({} characters, max {})",
line_num + 1,
line.len(),
max_line_length
)));
}
}
if message.contains(" ") { }
let suspicious_patterns = ["](javascript:", "](data:", "](vbscript:"];
for pattern in &suspicious_patterns {
if message.to_lowercase().contains(pattern) {
return Err(CliError::InputError(
"Message contains potentially unsafe URL patterns".to_string(),
));
}
}
Ok(())
}
pub fn validate_message_content(message: &str) -> CliResult<()> {
let max_repeated_chars = 50;
let mut current_char = '\0';
let mut repeat_count = 0;
for ch in message.chars() {
if ch == current_char {
repeat_count += 1;
if repeat_count > max_repeated_chars {
return Err(CliError::InputError(format!(
"Message contains excessive character repetition (more than {max_repeated_chars} consecutive '{ch}')"
)));
}
} else {
current_char = ch;
repeat_count = 1;
}
}
let uppercase_ratio = message
.chars()
.filter(|c| c.is_alphabetic())
.map(|c| if c.is_uppercase() { 1.0 } else { 0.0 })
.sum::<f64>()
/ message.chars().filter(|c| c.is_alphabetic()).count().max(1) as f64;
if uppercase_ratio > 0.8 && message.len() > 20 {
}
Ok(())
}
pub fn validate_message_context(message: &str, is_group_chat: bool) -> CliResult<()> {
if is_group_chat {
let mention_patterns = ["@everyone", "@channel", "@all"];
for pattern in &mention_patterns {
if message.to_lowercase().contains(pattern) {
}
}
}
let max_word_length = 200;
for word in message.split_whitespace() {
if word.len() > max_word_length {
return Err(CliError::InputError(format!(
"Message contains a word that is too long ({} characters, max {})",
word.len(),
max_word_length
)));
}
}
Ok(())
}
pub fn validate_message_comprehensive(message: &str, is_group_chat: bool) -> CliResult<()> {
validate_message_text(message)?;
validate_message_formatting(message)?;
validate_message_content(message)?;
validate_message_context(message, is_group_chat)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_message_text() {
assert!(validate_message_text("Hello, world!").is_ok());
assert!(validate_message_text("").is_err());
assert!(validate_message_text(" ").is_err());
assert!(validate_message_text("A").is_ok());
let long_message = "a".repeat(4097);
assert!(validate_message_text(&long_message).is_err());
let max_length_message = "a".repeat(4096);
assert!(validate_message_text(&max_length_message).is_ok());
}
#[test]
fn test_validate_message_id() {
assert!(validate_message_id("msg123").is_ok());
assert!(validate_message_id("msg_123").is_ok());
assert!(validate_message_id("msg-123").is_ok());
assert!(validate_message_id("123").is_ok());
assert!(validate_message_id("").is_err());
assert!(validate_message_id("msg@123").is_err());
assert!(validate_message_id("msg.123").is_err());
}
#[test]
fn test_validate_message_formatting() {
assert!(validate_message_formatting("Normal message").is_ok());
let long_line = "a".repeat(1001);
assert!(validate_message_formatting(&long_line).is_err());
assert!(validate_message_formatting("Click [here](javascript:alert('xss'))").is_err());
assert!(validate_message_formatting("Safe [link](https://example.com)").is_ok());
}
#[test]
fn test_validate_message_content() {
assert!(validate_message_content("Normal message").is_ok());
let repeated = "a".repeat(51);
assert!(validate_message_content(&repeated).is_err());
let acceptable_repeat = "a".repeat(50);
assert!(validate_message_content(&acceptable_repeat).is_ok());
}
#[test]
fn test_validate_message_context() {
assert!(validate_message_context("Hello everyone", true).is_ok());
assert!(validate_message_context("Hello everyone", false).is_ok());
let long_word = format!("This is a {} word", "a".repeat(201));
assert!(validate_message_context(&long_word, false).is_err());
}
#[test]
fn test_validate_message_with_null_bytes() {
let message_with_null = "Hello\0World";
assert!(validate_message_text(message_with_null).is_err());
}
#[test]
fn test_validate_message_comprehensive() {
assert!(validate_message_comprehensive("Hello, world!", false).is_ok());
assert!(validate_message_comprehensive("", false).is_err());
let problematic_message = "a".repeat(51); assert!(validate_message_comprehensive(&problematic_message, false).is_err());
}
}
#[cfg(test)]
mod prop_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_validate_message_text_random(s in ".{0,8192}") {
let _ = validate_message_text(&s);
}
#[test]
fn prop_validate_message_id_random(s in ".{0,256}") {
let _ = validate_message_id(&s);
}
#[test]
fn prop_validate_message_formatting_random(s in ".{0,4096}") {
let _ = validate_message_formatting(&s);
}
#[test]
fn prop_validate_message_content_random(s in ".{0,4096}") {
let _ = validate_message_content(&s);
}
#[test]
fn prop_validate_message_context_random(s in ".{0,4096}", is_group in proptest::bool::ANY) {
let _ = validate_message_context(&s, is_group);
}
}
}