network_operations.rs
1 use regex::Regex; 2 use reqwest::blocking::{Client, Response}; 3 use serde::{Deserialize, Serialize}; 4 use std::{thread, time::Duration}; 5 6 use crate::{clear_screen, encryption::decrypt_data, MessageData}; 7 8 // Structures for public keys and ciphertext 9 #[derive(Serialize, Deserialize)] 10 struct Message { 11 message: String, 12 room_id: String, 13 } 14 15 // Function to create the reqwest blocking client with proxy 16 pub fn create_client_with_proxy(proxy: &str) -> Client { 17 // Create the reqwest client with custom transport handling the proxy 18 let transport = reqwest::blocking::Client::builder() 19 .danger_accept_invalid_certs(false) // Reject invalid certificates 20 .proxy(reqwest::Proxy::all(proxy).expect("Invalid proxy address")) // Set proxy 21 // Route through Tor/I2P proxy 22 .build() 23 .unwrap(); 24 25 transport 26 } 27 28 pub fn fetch_kyber_pubkey(password: &str, server_url: &str) -> Option<String> { 29 // Check if the server URL contains `.i2p` 30 let proxy = if server_url.contains(".i2p") { 31 "http://127.0.0.1:4444" // I2P Proxy address 32 } else { 33 "socks5h://127.0.0.1:9050" // SOCKS5 Proxy address (Tor) 34 }; 35 let client = create_client_with_proxy(proxy); 36 37 let url = format!("{}/messages?room_id={}", server_url, password); 38 let mut retries = 0; 39 let max_retries = 3; 40 41 loop { 42 let res: Response = match client.get(&url).timeout(Duration::from_secs(60)).send() { 43 Ok(response) => response, 44 Err(_) => { 45 retries += 1; 46 if retries > max_retries { 47 return None; // Return None after 3 failed attempts 48 } 49 println!("Error while fetching public key. Retrying..."); 50 thread::sleep(Duration::from_secs(2)); // Wait before retrying 51 continue; 52 } 53 }; 54 55 if res.status().is_success() { 56 let body = match res.text() { 57 Ok(text) => text, 58 Err(_) => { 59 retries += 1; 60 if retries > max_retries { 61 return None; 62 } 63 println!("Error while reading response body. Retrying..."); 64 thread::sleep(Duration::from_secs(2)); // Wait before retrying 65 continue; 66 } 67 }; 68 69 if let Some(public_key_start) = body.find("KYBER_PUBLIC_KEY:") { 70 let public_key = &body[public_key_start + "KYBER_PUBLIC_KEY:".len()..]; // Remove marker 71 if let Some(end_data) = public_key.find("[END DATA]") { 72 return Some(public_key[0..end_data].to_string()); // Remove [END DATA] marker 73 } 74 } 75 } 76 77 retries += 1; 78 if retries > max_retries { 79 return None; // Return None after 3 failed attempts 80 } 81 82 println!("Public key not found. Retrying..."); 83 thread::sleep(Duration::from_secs(2)); // Sleep for 2 seconds before retrying 84 } 85 } 86 87 pub fn fetch_dilithium_pubkeys(password: &str, server_url: &str) -> Vec<String> { 88 // Check if the server URL contains `.i2p` 89 let proxy = if server_url.contains(".i2p") { 90 "http://127.0.0.1:4444" // I2P Proxy address 91 } else { 92 "socks5h://127.0.0.1:9050" // SOCKS5 Proxy address (Tor) 93 }; 94 let client = create_client_with_proxy(proxy); 95 96 let url = format!("{}/messages?room_id={}", server_url, password); 97 let mut retries = 0; 98 let max_retries = 3; 99 100 loop { 101 let res: Response = match client.get(&url).timeout(Duration::from_secs(60)).send() { 102 Ok(response) => response, 103 Err(_) => { 104 retries += 1; 105 if retries > max_retries { 106 eprintln!("Failed to fetch public keys after {} retries.", max_retries); 107 return Vec::new(); // Return an empty vector on failure 108 } 109 println!("Error while fetching public keys. Retrying..."); 110 thread::sleep(Duration::from_secs(2)); // Wait before retrying 111 continue; 112 } 113 }; 114 115 if res.status().is_success() { 116 let body = match res.text() { 117 Ok(text) => text, 118 Err(_) => { 119 retries += 1; 120 if retries > max_retries { 121 eprintln!("Failed to read response body after {} retries.", max_retries); 122 return Vec::new(); 123 } 124 println!("Error while reading response body. Retrying..."); 125 thread::sleep(Duration::from_secs(2)); // Wait before retrying 126 continue; 127 } 128 }; 129 130 let mut public_keys = Vec::new(); 131 for key_data in body.split("DILITHIUM_PUBLIC_KEY:") { 132 if let Some(end_data) = key_data.find("[END DATA]") { 133 let key = key_data[0..end_data].trim().to_string(); 134 public_keys.push(key); 135 } 136 } 137 138 if !public_keys.is_empty() { 139 return public_keys; // Return all valid public keys 140 } 141 } 142 143 retries += 1; 144 if retries > max_retries { 145 eprintln!("Public keys not found after {} retries.", max_retries); 146 return Vec::new(); // Return an empty vector on failure 147 } 148 149 println!("No valid public keys found in response. Retrying..."); 150 thread::sleep(Duration::from_secs(2)); // Sleep for 2 seconds before retrying 151 } 152 } 153 154 pub fn fetch_eddsa_pubkeys(password: &str, server_url: &str) -> Vec<String> { 155 // Check if the server URL contains `.i2p` 156 let proxy = if server_url.contains(".i2p") { 157 "http://127.0.0.1:4444" // I2P Proxy address 158 } else { 159 "socks5h://127.0.0.1:9050" // SOCKS5 Proxy address (Tor) 160 }; 161 let client = create_client_with_proxy(proxy); 162 163 let url = format!("{}/messages?room_id={}", server_url, password); 164 let mut retries = 0; 165 let max_retries = 3; 166 167 loop { 168 let res: Response = match client.get(&url).timeout(Duration::from_secs(60)).send() { 169 Ok(response) => response, 170 Err(_) => { 171 retries += 1; 172 if retries > max_retries { 173 eprintln!("Failed to fetch public keys after {} retries.", max_retries); 174 return Vec::new(); // Return an empty vector on failure 175 } 176 println!("Error while fetching public keys. Retrying..."); 177 thread::sleep(Duration::from_secs(2)); // Wait before retrying 178 continue; 179 } 180 }; 181 182 if res.status().is_success() { 183 let body = match res.text() { 184 Ok(text) => text, 185 Err(_) => { 186 retries += 1; 187 if retries > max_retries { 188 eprintln!("Failed to read response body after {} retries.", max_retries); 189 return Vec::new(); 190 } 191 println!("Error while reading response body. Retrying..."); 192 thread::sleep(Duration::from_secs(2)); // Wait before retrying 193 continue; 194 } 195 }; 196 197 let mut public_keys = Vec::new(); 198 for key_data in body.split("EDDSA_PUBLIC_KEY:") { 199 if let Some(end_data) = key_data.find("[END DATA]") { 200 let key = key_data[0..end_data].trim().to_string(); 201 public_keys.push(key); 202 } 203 } 204 205 if !public_keys.is_empty() { 206 return public_keys; // Return all valid public keys 207 } 208 } 209 210 retries += 1; 211 if retries > max_retries { 212 eprintln!("Public keys not found after {} retries.", max_retries); 213 return Vec::new(); // Return an empty vector on failure 214 } 215 216 println!("No valid public keys found in response. Retrying..."); 217 thread::sleep(Duration::from_secs(2)); // Sleep for 2 seconds before retrying 218 } 219 } 220 221 pub fn fetch_ciphertext(password: &str, server_url: &str) -> String { 222 // Check if the server URL contains `.i2p` 223 let proxy = if server_url.contains(".i2p") { 224 "http://127.0.0.1:4444" // I2P Proxy address 225 } else { 226 "socks5h://127.0.0.1:9050" // SOCKS5 Proxy address (Tor) 227 }; 228 let client = create_client_with_proxy(proxy); 229 230 let url = format!("{}/messages?room_id={}", server_url, password); 231 232 loop { 233 let res: Response = match client.get(&url).timeout(Duration::from_secs(60)).send() { 234 Ok(response) => response, 235 Err(err) => { 236 println!("Error while fetching ciphertext: {}. Retrying...", err); 237 thread::sleep(Duration::from_secs(2)); // Wait before retrying 238 continue; 239 } 240 }; 241 242 if res.status().is_success() { 243 let body = match res.text() { 244 Ok(text) => text, 245 Err(err) => { 246 println!("Error while reading response body: {}. Retrying...", err); 247 thread::sleep(Duration::from_secs(2)); // Wait before retrying 248 continue; 249 } 250 }; 251 252 if let Some(ciphertext_start) = body.find("KYBER_PUBLIC_KEY:CIPHERTEXT:") { 253 let ciphertext = &body[ciphertext_start + "KYBER_PUBLIC_KEY:CIPHERTEXT:".len()..]; // Remove marker 254 if let Some(end_data) = ciphertext.find("[END DATA]") { 255 return ciphertext[0..end_data].to_string(); // Remove [END DATA] marker 256 } 257 } 258 } 259 260 // Wait for 2 seconds before retrying 261 println!("Ciphertext not found. Retrying..."); 262 thread::sleep(Duration::from_secs(2)); // Sleep for 2 seconds before retrying 263 } 264 } 265 266 pub fn send_kyber_pubkey(room_id: &str, public_key: &str, url: &str) { 267 // Check if the server URL contains `.i2p` 268 let proxy = if url.contains(".i2p") { 269 "http://127.0.0.1:4444" // I2P Proxy address 270 } else { 271 "socks5h://127.0.0.1:9050" // SOCKS5 Proxy address (Tor) 272 }; 273 let client = create_client_with_proxy(proxy); 274 275 let full_url = format!("{}/send", url); // Append /send to the URL 276 let message = Message { 277 message: format!("KYBER_PUBLIC_KEY:{}[END DATA]", public_key), 278 room_id: room_id.to_string(), 279 }; 280 281 let res = client.post(&full_url).json(&message).timeout(Duration::from_secs(60)).send(); // Use the full URL 282 283 match res { 284 Ok(response) if response.status().is_success() => { 285 println!("Kyber1024 public key sent successfully!"); 286 } 287 Ok(response) => { 288 println!("Failed to send public key. Status: {}", response.status()); 289 } 290 Err(e) => { 291 println!("Failed to send public key. Error: {}", e); 292 } 293 } 294 } 295 296 pub fn send_dilithium_pubkey(room_id: &str, public_key: &str, url: &str) { 297 // Check if the server URL contains `.i2p` 298 let proxy = if url.contains(".i2p") { 299 "http://127.0.0.1:4444" // I2P Proxy address 300 } else { 301 "socks5h://127.0.0.1:9050" // SOCKS5 Proxy address (Tor) 302 }; 303 let client = create_client_with_proxy(proxy); 304 305 let full_url = format!("{}/send", url); // Append /send to the URL 306 let message = Message { 307 message: format!("DILITHIUM_PUBLIC_KEY:{}[END DATA]", public_key), 308 room_id: room_id.to_string(), 309 }; 310 311 let res = client.post(&full_url).json(&message).timeout(Duration::from_secs(60)).send(); // Use the full URL 312 313 match res { 314 Ok(response) if response.status().is_success() => { 315 println!("Dilithium5 public key sent successfully!"); 316 } 317 Ok(response) => { 318 println!("Failed to send public key. Status: {}", response.status()); 319 } 320 Err(e) => { 321 println!("Failed to send public key. Error: {}", e); 322 } 323 } 324 } 325 326 pub fn send_eddsa_pubkey(room_id: &str, public_key: &str, url: &str) { 327 // Check if the server URL contains `.i2p` 328 let proxy = if url.contains(".i2p") { 329 "http://127.0.0.1:4444" // I2P Proxy address 330 } else { 331 "socks5h://127.0.0.1:9050" // SOCKS5 Proxy address (Tor) 332 }; 333 let client = create_client_with_proxy(proxy); 334 335 let full_url = format!("{}/send", url); // Append /send to the URL 336 let message = Message { 337 message: format!("EDDSA_PUBLIC_KEY:{}[END DATA]", public_key), 338 room_id: room_id.to_string(), 339 }; 340 341 let res: Response = match client.post(&full_url).json(&message).timeout(Duration::from_secs(60)).send() { 342 Ok(response) => response, 343 Err(_) => { 344 println!("Failed to send the public key."); 345 return; 346 } 347 }; 348 349 if res.status().is_success() { 350 println!("EdDSA public key sent successfully!"); 351 } else { 352 println!("Failed to send public key."); 353 } 354 } 355 356 pub fn send_ciphertext(room_id: &str, ciphertext: &str, url: &str) { 357 // Check if the server URL contains `.i2p` 358 let proxy = if url.contains(".i2p") { 359 "http://127.0.0.1:4444" // I2P Proxy address 360 } else { 361 "socks5h://127.0.0.1:9050" // SOCKS5 Proxy address (Tor) 362 }; 363 let client = create_client_with_proxy(proxy); 364 365 let full_url = format!("{}/send", url); // Append /send to the URL 366 let message = Message { 367 message: format!("KYBER_PUBLIC_KEY:CIPHERTEXT:{}[END DATA]", ciphertext), 368 room_id: room_id.to_string(), 369 }; 370 371 let res: Response = client.post(&full_url).json(&message).timeout(Duration::from_secs(60)).send().unwrap(); // Use the full URL 372 373 if res.status().is_success() { 374 println!("Ciphertext sent successfully!"); 375 } else { 376 println!("Failed to send ciphertext"); 377 } 378 } 379 380 pub fn send_encrypted_message( 381 encrypted_message: &str, 382 room_id: &str, 383 server_url: &str, 384 ) -> Result<(), Box<dyn std::error::Error>> { 385 // Check if the server URL contains `.i2p` 386 let proxy = if server_url.contains(".i2p") { 387 "http://127.0.0.1:4444" // I2P Proxy address 388 } else { 389 "socks5h://127.0.0.1:9050" // SOCKS5 Proxy address (Tor) 390 }; 391 let client = create_client_with_proxy(proxy); 392 393 // Format the encrypted message with the BEGIN and END markers 394 let formatted_encrypted_message = format!( 395 "-----BEGIN ENCRYPTED MESSAGE-----{}-----END ENCRYPTED MESSAGE-----", 396 encrypted_message 397 ); 398 399 // Create the message data to send 400 let message_data = MessageData { 401 message: formatted_encrypted_message, 402 room_id: room_id.to_string(), 403 }; 404 405 // Construct the full URL for sending the message 406 let send_url = format!("{}/send", server_url); 407 408 // Send the message via HTTP POST request 409 let res = client 410 .post(&send_url) 411 .json(&message_data) 412 .timeout(Duration::from_secs(60)) // Set a timeout for the request 413 .send()?; 414 415 // Check if the request was successful and print the result 416 if res.status().is_success() { 417 println!("Message sent successfully."); 418 } else { 419 eprintln!("Failed to send message: {}", res.status()); 420 } 421 422 Ok(()) 423 } 424 425 pub fn receive_and_fetch_messages( 426 room_id: &str, 427 shared_secret: &str, 428 server_url: &str, 429 gui: bool, 430 ) -> Result<Vec<String>, Box<dyn std::error::Error>> { 431 // SOCKS5 Proxy setup 432 // Check if the server URL contains `.i2p` 433 let proxy = if server_url.contains(".i2p") { 434 "http://127.0.0.1:4444" // I2P Proxy address 435 } else { 436 "socks5h://127.0.0.1:9050" // SOCKS5 Proxy address (Tor) 437 }; 438 let client = create_client_with_proxy(proxy); 439 440 // Build the URL with the provided room password and server URL 441 let url = format!("{}/messages?room_id={}", server_url, room_id); 442 443 // Send a synchronous GET request to fetch messages 444 let res = client 445 .get(&url) 446 .timeout(std::time::Duration::from_secs(30)) // Set a timeout for the request 447 .send()?; 448 449 // Declare the vector to store messages outside the response block 450 let mut messages = Vec::new(); 451 452 // Check if the request was successful 453 if res.status().is_success() { 454 clear_screen(); 455 // Get the body of the HTML response 456 let body = res.text()?; 457 458 // Define a regular expression to capture messages between the markers 459 let re = Regex::new(r"-----BEGIN ENCRYPTED MESSAGE-----\s*(.*?)\s*-----END ENCRYPTED MESSAGE-----") 460 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?; 461 462 // Iterate over all matches in the HTML body 463 for cap in re.captures_iter(&body) { 464 if let Some(encrypted_message) = cap.get(1) { 465 // Step 1: Get the encrypted message without the markers 466 let cleaned_message = encrypted_message.as_str().trim(); 467 468 // Step 2: Decrypt the message (ignore the markers, only pass the actual content) 469 match decrypt_data(cleaned_message, shared_secret) { 470 Ok(decrypted_message) => { 471 fn unpad_message(message: &str) -> String { 472 // Remove everything inside the <padding>...</padding> part and trim any extra spaces from the message 473 if let Some(start) = message.find("<padding>") { 474 if let Some(end) = message.find("</padding>") { 475 let (message_before_padding, _) = message.split_at(start); // Part before <padding> 476 let (_, message_after_padding) = message.split_at(end + 10); // Skip past </padding> (length 10 including '>') 477 return format!("{}{}", message_before_padding, message_after_padding); 478 } 479 } 480 message.to_string() // In case there are no <padding>...</padding> markers 481 } 482 483 let unpadded_message = unpad_message(&decrypted_message); 484 485 // Ignore messages containing `[DUMMY_DATA]:` 486 if unpadded_message.contains("[DUMMY_DATA]:") { 487 continue; 488 } 489 490 // If gui is false, skip messages containing `<media>` 491 if !gui && unpadded_message.contains("<media>") { 492 continue; 493 } 494 495 // If gui is false, skip messages containing `<pfp>` 496 if !gui && unpadded_message.contains("<pfp>") { 497 continue; 498 } 499 500 // If gui is true, do not replace <strong> tags 501 let final_message = if gui { 502 unpadded_message.to_string() 503 } else { 504 // Step 3: Replace <strong> tags with ANSI escape codes for bold text 505 let strong_re = Regex::new(r"<strong>(.*?)</strong>").unwrap(); 506 strong_re.replace_all(&unpadded_message, |caps: ®ex::Captures| { 507 // Replace <strong>...</strong> with ANSI escape codes for bold text 508 format!("\x1b[1m{}\x1b[0m", &caps[1]) 509 }).to_string() 510 }; 511 512 // Add the messages to the list 513 messages.push(final_message); 514 } 515 Err(_e) => { 516 // Ignore decryption failure 517 } 518 } 519 } 520 } 521 } else { 522 // Provide more detailed error info for failed requests 523 eprintln!("Failed to fetch messages: {} - {}", res.status(), res.text()?); 524 } 525 526 // Return the collected messages 527 Ok(messages) 528 }