main.rs
1 #[macro_use] 2 extern crate rocket; 3 4 use rocket::response::{Redirect, content::RawHtml}; 5 use rocket::serde::{Serialize, Deserialize}; 6 use rocket::State; 7 use rocket::http::Status; 8 use std::collections::{HashMap, HashSet}; 9 use std::sync::Arc; 10 use tokio::sync::Mutex; 11 use tokio::time::{interval, Duration}; 12 use std::time::{SystemTime, UNIX_EPOCH}; 13 use html_escape::encode_text; 14 use tokio::time::sleep; 15 use zeroize::Zeroize; 16 use rocket::serde::json::Json; 17 18 mod encryption; 19 use crate::encryption::{encrypt_message, decrypt_message, is_message_encrypted}; 20 21 // Constants 22 const TIME_WINDOW: u64 = 60; 23 const MESSAGE_LIMIT: usize = 20; // 20 messages in 60 seconds 24 const MAX_MESSAGE_LENGTH: usize = 2 * 1024 * 1024; // 2 megabytes 25 const RECENT_MESSAGE_LIMIT: usize = 1000; // Maximum number of messages 26 const MESSAGE_EXPIRY_DURATION: u64 = 86400; // 1 day 27 28 #[derive(Debug, Clone, Serialize, Deserialize)] 29 struct Message { 30 content: String, 31 timestamp: u64, 32 } 33 34 #[derive(Debug, Deserialize)] 35 struct MessageData { 36 message: String, 37 room_id: String, 38 } 39 40 #[derive(Debug)] 41 struct ChatState { 42 messages: Arc<Mutex<Vec<Message>>>, 43 user_request_timestamps: Arc<Mutex<HashMap<String, (u64, u64)>>>, 44 recent_messages: Arc<Mutex<HashSet<String>>>, 45 global_message_timestamps: Arc<Mutex<Vec<u64>>>, 46 } 47 48 // Manually implement Clone for ChatState with Arc 49 impl Clone for ChatState { 50 fn clone(&self) -> Self { 51 ChatState { 52 messages: Arc::clone(&self.messages), 53 user_request_timestamps: Arc::clone(&self.user_request_timestamps), 54 recent_messages: Arc::clone(&self.recent_messages), 55 global_message_timestamps: Arc::clone(&self.global_message_timestamps), 56 } 57 } 58 } 59 60 // Helper function to format the timestamp into HH:MM:SS 61 fn format_timestamp(timestamp: u64) -> String { 62 let seconds = timestamp % 60; 63 let minutes = (timestamp / 60) % 60; 64 let hours = (timestamp / 3600) % 24; 65 format!("{:02}:{:02}:{:02}", hours, minutes, seconds) 66 } 67 68 // Function to check if the message count exceeds the limit globally 69 async fn check_message_limit(state: &ChatState) -> bool { 70 let mut global_timestamps = state.global_message_timestamps.lock().await; 71 let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); 72 73 // Remove messages older than the time window (60 seconds) 74 global_timestamps.retain(|×tamp| current_time - timestamp <= TIME_WINDOW); 75 76 // Check if we have exceeded the message limit (20 messages in 60 seconds) 77 if global_timestamps.len() >= MESSAGE_LIMIT { 78 return false; // Exceeded the limit 79 } 80 81 // Record the current message timestamp 82 global_timestamps.push(current_time); 83 true 84 } 85 86 // Function to check if the message is valid (length and total message count) 87 async fn is_message_valid(message: &str, state: &ChatState) -> bool { 88 // Check if the message length exceeds the maximum limit 89 if message.len() > MAX_MESSAGE_LENGTH { 90 return false; 91 } 92 93 // Lock the messages state to access the total message count 94 let mut messages = state.messages.lock().await; 95 96 // Check if the total number of messages exceeds the limit 97 if messages.len() >= RECENT_MESSAGE_LIMIT { 98 // Wipe the content of the oldest message before removing it 99 wipe_message_content(&mut messages[0]); 100 messages.remove(0); // Remove the first message in the vector (oldest) 101 } 102 103 true 104 } 105 106 #[get("/messages?<room_id>")] 107 async fn messages(room_id: Option<String>, state: &State<Arc<ChatState>>) -> String { 108 let chat_state = state.inner(); 109 let messages = chat_state.messages.lock().await; 110 111 let mut html = String::new(); 112 for message in messages.iter() { 113 // Format timestamp for display 114 let timestamp = format_timestamp(message.timestamp); 115 116 // Decrypt message content based on the provided room_id, if available 117 let decrypted_content = match &room_id { 118 Some(ref pw) => decrypt_message(&message.content, pw).unwrap_or_else(|_| { 119 // Return nothing when decryption fails 120 return String::new(); // Empty string will effectively remove the message from HTML 121 }), 122 None => String::new(), // No room_id provided, return empty content 123 }; 124 125 // If decryption fails (decrypted_content is empty), skip appending the message 126 if decrypted_content.is_empty() { 127 continue; // Skip this message entirely 128 } 129 130 // Display the decrypted content, or nothing if decryption failed 131 html.push_str(&format!( 132 r#"<p>[{}]: {}</p>"#, 133 timestamp, 134 encode_text(&decrypted_content) 135 )); 136 } 137 138 html 139 } 140 141 #[get("/?<room_id>")] 142 async fn index(room_id: Option<String>, state: &State<Arc<ChatState>>) -> Result<RawHtml<String>, Status> { 143 // Read the static HTML template 144 let mut html = tokio::fs::read_to_string("static/index.html") 145 .await 146 .map_err(|_error| Status::InternalServerError)?; 147 148 // Get room_id, defaulting to empty string if not provided 149 // Safely handle temporary value by assigning them to variable 150 let room_id_value = room_id.clone().unwrap_or_else(|| "".to_string()); 151 152 // Safely encode them using encode_text. 153 let encoded_room_id = encode_text(&room_id_value); 154 155 // Replace placeholder with actual values 156 html = html.replace("room_id_PLACEHOLDER", &encoded_room_id); 157 158 // Get current chat messages and generate HTML for them 159 let messages = state.messages.lock().await; 160 let mut messages_html = String::new(); 161 162 for msg in messages.iter() { 163 let timestamp = format_timestamp(msg.timestamp); 164 165 // Decrypt message content based on provided room_id 166 let decrypted_content = if let Some(ref pw) = room_id { 167 decrypt_message(&msg.content, pw).unwrap_or_else(|_| "Decryption failed".to_string()) 168 } else { 169 "room_id not provided".to_string() 170 }; 171 172 // Add the message to the HTML string 173 messages_html.push_str(&format!( 174 "<p>[{}]: {}</p>", 175 timestamp, 176 encode_text(&decrypted_content) 177 )); 178 } 179 180 // Insert messages into the HTML template 181 html = html.replace("<!-- Messages will be dynamically inserted here -->", &messages_html); 182 183 // Return the final HTML to the client 184 Ok(RawHtml(html)) 185 } 186 187 // Route for sending a message with encryption 188 #[post("/send", data = "<message_data>")] 189 async fn send(message_data: Json<MessageData>, state: &State<Arc<ChatState>>) -> Result<Redirect, RawHtml<String>> { 190 let message = message_data.message.trim(); 191 let room_id = message_data.room_id.trim(); 192 193 // Delay message processing by 2 seconds 194 sleep(Duration::from_secs(2)).await; 195 196 // Check if the message limit has been exceeded globally 197 if !check_message_limit(&state.inner()).await { 198 return Err(RawHtml("Too many messages sent in a short period. Please wait for 2 minutes.".to_string())); 199 } 200 201 // Reject the request if the room room_id field is empty 202 if room_id.is_empty() { 203 return Err(RawHtml("Room room_id cannot be empty. Please provide a room_id.".to_string())); 204 } 205 206 // Check if the room_id is at least 8 characters long 207 if room_id.len() < 8 { 208 return Err(RawHtml("Room room_id must be at least 8 characters long.".to_string())); 209 } 210 211 // Check if the message is valid (length and total message count) 212 if !is_message_valid(message, state).await { 213 return Err(RawHtml("Invalid message. Make sure it's less than 10MB.".to_string())); 214 } 215 216 // Check if the message is encrypted 217 if !is_message_encrypted(message) { 218 return Err(RawHtml("Message is not encrypted. Please provide an encrypted message.".to_string())); 219 } 220 221 let mut messages = state.messages.lock().await; 222 let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); 223 224 let encrypted_content = encrypt_message(message, room_id).map_err(|_| RawHtml("Encryption failed.".to_string()))?; 225 226 messages.push(Message { 227 content: encrypted_content, 228 timestamp, 229 }); 230 231 Ok(Redirect::to(format!("/"))) 232 } 233 234 // Function to wipe message content securely 235 fn wipe_message_content(message: &mut Message) { 236 // Securely zero out the message content 237 message.content.zeroize(); 238 } 239 240 // Cleanup task to remove expired messages and securely wipe their contents 241 async fn message_cleanup_task(state: Arc<ChatState>) { 242 let mut interval = interval(Duration::from_secs(1)); // Check every second 243 244 loop { 245 interval.tick().await; // Wait for the next tick of the interval 246 247 let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); 248 249 // Acquire the lock on messages 250 let mut messages = state.messages.lock().await; 251 252 // Check if there are messages that should be wiped 253 if let Some(oldest_message_index) = messages.iter().position(|message| { 254 current_time - message.timestamp >= MESSAGE_EXPIRY_DURATION 255 }) { 256 // Securely wipe the content of the oldest message 257 wipe_message_content(&mut messages[oldest_message_index]); 258 259 // Remove the oldest message 260 messages.remove(oldest_message_index); 261 } 262 } 263 } 264 // Main function to launch the Rocket server 265 #[tokio::main] 266 async fn main() { 267 let chat_state = Arc::new(ChatState { 268 messages: Arc::new(Mutex::new(Vec::new())), 269 user_request_timestamps: Arc::new(Mutex::new(HashMap::new())), 270 recent_messages: Arc::new(Mutex::new(HashSet::new())), 271 global_message_timestamps: Arc::new(Mutex::new(Vec::new())), 272 }); 273 274 // Launch the message cleanup task 275 tokio::spawn(message_cleanup_task(Arc::clone(&chat_state))); 276 277 // Launch the Rocket application 278 rocket::build() 279 .manage(chat_state) 280 .mount("/", routes![index, send, messages]) 281 .mount("/static", rocket::fs::FileServer::from("static")) 282 .launch() 283 .await 284 .unwrap(); // Ensure the Rocket server is awaited and handle any errors 285 }