Compare commits

...

12 Commits
1.0.0 ... main

Author SHA1 Message Date
db20437749 Fix: fix on_send_auth_message message text 2025-06-22 19:33:47 +03:00
edd9085304 add server-info packet 2025-06-22 11:45:37 +03:00
3c6f9fb006 remove dbg! 2025-06-20 01:59:31 +03:00
a5120ddb82 update bRAC commit id 2025-06-20 01:58:33 +03:00
83d1726969 Feature: Proxy-Mode 2025-06-19 23:51:42 +03:00
f4ad746453 make using rac url 2025-06-19 23:08:44 +03:00
a5ea227748 dont clone anything if you dont want to ok yes bro 2025-06-18 16:30:27 +03:00
d3bb035fda move read_u32 and read_string lambdas back to where they must to be 2025-06-18 09:14:54 +03:00
MeexReay
ae9fb60eab
Merge pull request #1 from kostya-zero/main
refactor: remove unnecessary cloning and simplify code
2025-06-18 09:03:01 +03:00
245c677c4c brac import and more readme stuff 2025-06-18 08:56:02 +03:00
Konstantin Zhigaylo
87c5a7ce42
refactor: remove unnecessary cloning and simplify code 2025-06-18 08:13:14 +03:00
f262536e39 move format_message and sanitize_text from old bRAC version 2025-06-18 06:41:40 +03:00
12 changed files with 851 additions and 222 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/result /result
/messages.txt /messages.txt
/accounts.txt /accounts.txt
/.idea

645
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,18 @@ edition = "2024"
[dependencies] [dependencies]
chrono = "0.4.40" chrono = "0.4.40"
bRAC = { git = "https://github.com/MeexReay/bRAC.git", default-features = false, tag = "0.1.2+2.0" }
md-5 = "0.10.6" md-5 = "0.10.6"
rand = "0.9.0" rand = "0.9.1"
clap = { version = "4.5.36", features = ["derive"] } clap = { version = "4.5.36", features = ["derive"] }
rustls = "0.23.25" rustls = "0.23.25"
tungstenite = "0.27.0" tungstenite = "0.27.0"
colog = "1.3.0" colog = "1.3.0"
log = "0.4.27" log = "0.4.27"
regex = "1.11.1"
colored = "3.0.0"
lazy_static = "1.5.0"
[dependencies.bRAC]
git = "https://github.com/MeexReay/bRAC"
rev = "f3b6cbd01c5443e03d012c3c866487ba19785baf"
default-features = false

View File

@ -1,29 +1,32 @@
# sRAC # sRAC
simple server for RAC simple server for RAC
## Usage ## features
- RACv2.0 and WRACv2.0 protocols
- SSL encryption (via rustls)
- messages limits by size
- splash message
- message sanitizing (removes all shit)
- auth-only mode and accounts
- messages saving into file
- register and message timeouts
## usage
```bash ```bash
git clone https://github.com/MeexReay/sRAC git clone https://github.com/MeexReay/sRAC.git; cd sRAC
cd sRAC cargo run -- -H rac://127.0.0.1:42666
cargo run -- -H 127.0.0.1:42666
``` ```
### My server config ## roadmap
```bash
cargo run -- \
--host 127.0.0.1:42666 \
--splash "please register (/register and /login commands in bRAC)" \
--messages-file "messages.txt" \
--accounts-file "accounts.txt" \
--register-timeout 3600 \
--sanitize \
--auth-only
```
## Roadmap
- [ ] Notifications by ip
- [ ] Server commands
- [x] WRAC protocol - [x] WRAC protocol
- [x] RACS protocol - [x] RACS protocol
- [x] Proxy-mode
- [ ] Notifications by ip (private messages)
- [ ] Server commands
## license
This project is licensed under the WTFPL. Do what the fuck you want to.

4
shell.nix Normal file
View File

@ -0,0 +1,4 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [ pkg-config openssl ];
}

View File

@ -11,13 +11,15 @@ use std::{
time::Duration, time::Duration,
}; };
use bRAC::{chat::format_message, util::sanitize_text};
use chrono::{DateTime, Local, TimeZone}; use chrono::{DateTime, Local, TimeZone};
use log::info; use log::info;
use md5::{Digest, Md5}; use md5::{Digest, Md5};
use rand::{Rng, distr::Alphanumeric}; use rand::{Rng, distr::Alphanumeric};
use crate::Args; use crate::{
Args,
util::{format_message, read_string, read_u32, sanitize_text},
};
fn load_accounts(accounts_file: Option<String>) -> Vec<Account> { fn load_accounts(accounts_file: Option<String>) -> Vec<Account> {
if let Some(accounts_file) = accounts_file.clone() { if let Some(accounts_file) = accounts_file.clone() {
@ -69,8 +71,8 @@ impl Context {
args, args,
messages_file: messages_file.clone(), messages_file: messages_file.clone(),
accounts_file: accounts_file.clone(), accounts_file: accounts_file.clone(),
messages: RwLock::new(load_messages(messages_file.clone())), messages: RwLock::new(load_messages(messages_file)),
accounts: RwLock::new(load_accounts(accounts_file.clone())), accounts: RwLock::new(load_accounts(accounts_file)),
messages_offset: AtomicU64::default(), messages_offset: AtomicU64::default(),
notifications: RwLock::new(HashMap::new()), notifications: RwLock::new(HashMap::new()),
timeouts: RwLock::new(HashMap::new()), timeouts: RwLock::new(HashMap::new()),
@ -80,7 +82,6 @@ impl Context {
pub fn push_message(&self, msg: Vec<u8>) -> Result<(), Box<dyn Error>> { pub fn push_message(&self, msg: Vec<u8>) -> Result<(), Box<dyn Error>> {
if let Some(messages_file) = self.messages_file.clone() { if let Some(messages_file) = self.messages_file.clone() {
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true)
.append(true) .append(true)
.create(true) .create(true)
.open(messages_file)?; .open(messages_file)?;
@ -110,27 +111,26 @@ impl Context {
} }
pub fn get_account_by_addr(&self, addr: &str) -> Option<Account> { pub fn get_account_by_addr(&self, addr: &str) -> Option<Account> {
for acc in self.accounts.read().unwrap().iter().rev() { self.accounts
if acc.addr() == addr { .read()
return Some(acc.clone()); .unwrap()
} .iter()
} .find(|acc| acc.addr() == addr)
None .cloned()
} }
pub fn get_account(&self, name: &str) -> Option<Account> { pub fn get_account(&self, name: &str) -> Option<Account> {
for acc in self.accounts.read().unwrap().iter() { self.accounts
if acc.name() == name { .read()
return Some(acc.clone()); .unwrap()
} .iter()
} .find(|acc| acc.name() == name)
None .cloned()
} }
pub fn push_account(&self, acc: Account) -> Result<(), Box<dyn Error>> { pub fn push_account(&self, acc: Account) -> Result<(), Box<dyn Error>> {
if let Some(accounts_file) = self.accounts_file.clone() { if let Some(accounts_file) = self.accounts_file.clone() {
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true)
.append(true) .append(true)
.create(true) .create(true)
.open(accounts_file)?; .open(accounts_file)?;
@ -213,41 +213,21 @@ impl Account {
} }
pub fn from_bytes(text: Vec<u8>) -> Result<Self, Box<dyn Error>> { pub fn from_bytes(text: Vec<u8>) -> Result<Self, Box<dyn Error>> {
let mut text = Cursor::new(text); let mut cursor = Cursor::new(text);
let mut name_len = [0; 4]; let name_len = read_u32(&mut cursor)? as usize;
text.read_exact(&mut name_len)?; let salt_len = read_u32(&mut cursor)? as usize;
let name_len = u32::from_le_bytes(name_len) as usize; let addr_len = read_u32(&mut cursor)? as usize;
let pass_len = read_u32(&mut cursor)? as usize;
let mut salt_len = [0; 4]; let name = read_string(&mut cursor, name_len)?;
text.read_exact(&mut salt_len)?; let salt = read_string(&mut cursor, salt_len)?;
let salt_len = u32::from_le_bytes(salt_len) as usize; let addr = read_string(&mut cursor, addr_len)?;
let mut addr_len = [0; 4];
text.read_exact(&mut addr_len)?;
let addr_len = u32::from_le_bytes(addr_len) as usize;
let mut pass_len = [0; 4];
text.read_exact(&mut pass_len)?;
let pass_len = u32::from_le_bytes(pass_len) as usize;
let mut name = vec![0; name_len];
text.read_exact(&mut name)?;
let name = String::from_utf8_lossy(&name).to_string();
let mut salt = vec![0; salt_len];
text.read_exact(&mut salt)?;
let salt = String::from_utf8_lossy(&salt).to_string();
let mut addr = vec![0; addr_len];
text.read_exact(&mut addr)?;
let addr = String::from_utf8_lossy(&addr).to_string();
let mut pass = vec![0; pass_len]; let mut pass = vec![0; pass_len];
text.read_exact(&mut pass)?; cursor.read_exact(&mut pass)?;
let mut date = [0; 8]; let mut date = [0; 8];
text.read_exact(&mut date)?; cursor.read_exact(&mut date)?;
let date = i64::from_le_bytes(date); let date = i64::from_le_bytes(date);
Ok(Account { Ok(Account {

View File

@ -4,12 +4,24 @@ use std::{
sync::{Arc, atomic::Ordering}, sync::{Arc, atomic::Ordering},
}; };
use bRAC::proto::{connect, read_messages, register_user, send_message, send_message_auth};
use chrono::Local; use chrono::Local;
use log::info; use log::info;
use crate::ctx::{Account, Context, add_message}; use crate::ctx::{Account, Context, add_message};
pub fn on_total_size(ctx: Arc<Context>, _: SocketAddr) -> Result<u64, Box<dyn Error>> { pub fn on_total_size(ctx: Arc<Context>, _: SocketAddr) -> Result<u64, Box<dyn Error>> {
if let Some(url) = ctx.args.proxy_to.as_ref() {
return read_messages(
&mut connect(url, ctx.args.use_proxy.clone())?,
1024, // TODO: softcode this
0,
false,
)?
.map(|o| o.1 as u64)
.ok_or("err on reading in proxy mode".into()); // TODO: fix reading two times
}
let messages_len = ctx.messages.read().unwrap().len() as u64; let messages_len = ctx.messages.read().unwrap().len() as u64;
let offset = ctx.messages_offset.load(Ordering::SeqCst); let offset = ctx.messages_offset.load(Ordering::SeqCst);
@ -23,8 +35,19 @@ pub fn on_total_size(ctx: Arc<Context>, _: SocketAddr) -> Result<u64, Box<dyn Er
pub fn on_total_data( pub fn on_total_data(
ctx: Arc<Context>, ctx: Arc<Context>,
_: SocketAddr, _: SocketAddr,
_: Option<u64>, // sent_size _sent_size: Option<u64>,
) -> Result<Vec<u8>, Box<dyn Error>> { ) -> Result<Vec<u8>, Box<dyn Error>> {
if let Some(url) = ctx.args.proxy_to.as_ref() {
return read_messages(
&mut connect(url, ctx.args.use_proxy.clone())?,
1024, // TODO: softcode this
0,
false,
)?
.map(|o| (o.0.join("\n") + "\n").as_bytes().to_vec())
.ok_or("err on reading in proxy mode".into()); // TODO: fix reading two times
}
let mut messages = ctx.messages.read().unwrap().clone(); let mut messages = ctx.messages.read().unwrap().clone();
let offset = ctx.messages_offset.load(Ordering::SeqCst); let offset = ctx.messages_offset.load(Ordering::SeqCst);
@ -46,9 +69,20 @@ pub fn on_total_data(
pub fn on_chunked_data( pub fn on_chunked_data(
ctx: Arc<Context>, ctx: Arc<Context>,
_: SocketAddr, _: SocketAddr,
_: Option<u64>, // sent_size _sent_size: Option<u64>,
client_has: u64, client_has: u64,
) -> Result<Vec<u8>, Box<dyn Error>> { ) -> Result<Vec<u8>, Box<dyn Error>> {
if let Some(url) = ctx.args.proxy_to.as_ref() {
return read_messages(
&mut connect(url, ctx.args.use_proxy.clone())?,
1024, // TODO: softcode this
client_has as usize,
true,
)?
.map(|o| (o.0.join("\n") + "\n").as_bytes().to_vec())
.ok_or("err on reading in proxy mode".into());
}
let messages = ctx.messages.read().unwrap().clone(); let messages = ctx.messages.read().unwrap().clone();
let offset = ctx.messages_offset.load(Ordering::SeqCst); let offset = ctx.messages_offset.load(Ordering::SeqCst);
let client_has = if let Some(splash) = &ctx.args.splash { let client_has = if let Some(splash) = &ctx.args.splash {
@ -70,11 +104,18 @@ pub fn on_send_message(
addr: SocketAddr, addr: SocketAddr,
message: Vec<u8>, message: Vec<u8>,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
if let Some(url) = ctx.args.proxy_to.as_ref() {
return send_message(
&mut connect(url, ctx.args.use_proxy.clone())?,
&String::from_utf8_lossy(&message),
); // TODO: make brac accept message in bytes
}
if !ctx.args.auth_only { if !ctx.args.auth_only {
let mut message = message; let mut message = message;
message.truncate(ctx.args.message_limit); message.truncate(ctx.args.message_limit);
add_message(&message.clone(), ctx.clone(), Some(addr.ip()))?; add_message(&message, ctx, Some(addr.ip()))?;
} }
Ok(()) Ok(())
} }
@ -86,18 +127,31 @@ pub fn on_send_auth_message(
password: &str, password: &str,
text: &str, text: &str,
) -> Result<Option<u8>, Box<dyn Error>> { ) -> Result<Option<u8>, Box<dyn Error>> {
if let Some(url) = ctx.args.proxy_to.as_ref() {
return match send_message_auth(
&mut connect(url, ctx.args.use_proxy.clone())?,
name,
password,
text,
) {
Ok(0) => Ok(None),
Ok(n) => Ok(Some(n)),
Err(err) => Err(err),
};
}
if let Some(acc) = ctx.get_account(name) { if let Some(acc) = ctx.get_account(name) {
if acc.check_password(password) { if acc.check_password(password) {
let mut name = name.to_string(); let mut name = name.to_string();
name.truncate(256); // FIXME: softcode this name.truncate(256); // TODO: softcode this
let mut password = password.to_string(); let mut password = password.to_string();
password.truncate(256); // FIXME: softcode this password.truncate(256); // TODO: softcode this
let mut text = text.to_string(); let mut text = text.to_string();
text.truncate(ctx.args.message_limit); text.truncate(ctx.args.message_limit);
add_message(&text.as_bytes(), ctx.clone(), None)?; add_message(format!("<{name}> {text}").as_bytes(), ctx, None)?;
Ok(None) Ok(None)
} else { } else {
@ -114,6 +168,19 @@ pub fn on_register_user(
name: &str, name: &str,
password: &str, password: &str,
) -> Result<Option<u8>, Box<dyn Error>> { ) -> Result<Option<u8>, Box<dyn Error>> {
if let Some(url) = ctx.args.proxy_to.as_ref() {
return Ok(
match register_user(
&mut connect(url, ctx.args.use_proxy.clone())?,
name,
password,
) {
Ok(true) => None,
_ => Some(0x01),
},
);
}
let addr = addr.ip().to_string(); let addr = addr.ip().to_string();
let now: i64 = Local::now().timestamp_millis(); let now: i64 = Local::now().timestamp_millis();
@ -136,3 +203,7 @@ pub fn on_register_user(
Ok(None) Ok(None)
} }
pub fn on_server_info(_: Arc<Context>, _: SocketAddr) -> Result<(u8, String), Box<dyn Error>> {
Ok((0x03, format!("sRAC {}", env!("CARGO_PKG_VERSION"))))
}

View File

@ -1,5 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use bRAC::proto::parse_rac_url;
use clap::Parser; use clap::Parser;
use log::info; use log::info;
@ -8,11 +9,12 @@ use crate::{ctx::Context, proto::run_listener};
pub mod ctx; pub mod ctx;
pub mod logic; pub mod logic;
pub mod proto; pub mod proto;
pub mod util;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version)] #[command(version)]
pub struct Args { pub struct Args {
/// Server host /// Server host (RAC URL)
#[arg(short = 'H', long)] #[arg(short = 'H', long)]
host: String, host: String,
@ -52,10 +54,6 @@ pub struct Args {
#[arg(long, default_value_t = 4194304)] #[arg(long, default_value_t = 4194304)]
messages_total_limit: usize, messages_total_limit: usize,
/// Enable SSL (RACS)
#[arg(short = 'l', long)]
enable_ssl: bool,
/// Set ssl certificate path (x509) /// Set ssl certificate path (x509)
#[arg(long)] #[arg(long)]
ssl_key: Option<String>, ssl_key: Option<String>,
@ -64,9 +62,13 @@ pub struct Args {
#[arg(long)] #[arg(long)]
ssl_cert: Option<String>, ssl_cert: Option<String>,
/// Enable WRAC /// Enable Proxy-Mode (RAC URL)
#[arg(short = 'w', long)] #[arg(short = 'P', long)]
enable_wrac: bool, proxy_to: Option<String>,
/// Use Socks5 proxy (to connect to the server in proxy-mode)
#[arg(long)]
use_proxy: Option<String>,
} }
fn main() { fn main() {
@ -82,5 +84,7 @@ fn main() {
info!("Server started on {}", &args.host); info!("Server started on {}", &args.host);
run_listener(context); let (host, ssl, wrac) = parse_rac_url(&args.host).expect("INVALID RAC URL");
run_listener(context, &host, ssl, wrac);
} }

View File

@ -24,8 +24,9 @@ fn accept_stream(
stream: impl Read + Write, stream: impl Read + Write,
addr: SocketAddr, addr: SocketAddr,
ctx: Arc<Context>, ctx: Arc<Context>,
wrac: bool,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
if ctx.args.enable_wrac { if wrac {
accept_wrac_stream(stream, addr, ctx)?; accept_wrac_stream(stream, addr, ctx)?;
} else { } else {
accept_rac_stream(stream, addr, ctx)?; accept_rac_stream(stream, addr, ctx)?;
@ -34,9 +35,8 @@ fn accept_stream(
Ok(()) Ok(())
} }
fn run_normal_listener(ctx: Arc<Context>) { fn run_normal_listener(ctx: Arc<Context>, host: &str, wrac: bool) {
let listener = let listener = TcpListener::bind(host).expect("error trying bind to the provided addr");
TcpListener::bind(&ctx.args.host).expect("error trying bind to the provided addr");
for stream in listener.incoming() { for stream in listener.incoming() {
let Ok(stream) = stream else { continue }; let Ok(stream) = stream else { continue };
@ -47,7 +47,7 @@ fn run_normal_listener(ctx: Arc<Context>) {
let Ok(addr) = stream.peer_addr() else { let Ok(addr) = stream.peer_addr() else {
return; return;
}; };
match accept_stream(stream, addr, ctx) { match accept_stream(stream, addr, ctx, wrac) {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
debug!("{}", e) debug!("{}", e)
@ -57,9 +57,8 @@ fn run_normal_listener(ctx: Arc<Context>) {
} }
} }
fn run_secure_listener(ctx: Arc<Context>) { fn run_secure_listener(ctx: Arc<Context>, host: &str, wrac: bool) {
let listener = let listener = TcpListener::bind(host).expect("error trying bind to the provided addr");
TcpListener::bind(&ctx.args.host).expect("error trying bind to the provided addr");
let server_config = Arc::new( let server_config = Arc::new(
ServerConfig::builder() ServerConfig::builder()
@ -101,7 +100,7 @@ fn run_secure_listener(ctx: Arc<Context>) {
}; };
} }
match accept_stream(stream, addr, ctx) { match accept_stream(stream, addr, ctx, wrac) {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
debug!("{}", e) debug!("{}", e)
@ -111,10 +110,10 @@ fn run_secure_listener(ctx: Arc<Context>) {
} }
} }
pub fn run_listener(ctx: Arc<Context>) { pub fn run_listener(ctx: Arc<Context>, host: &str, ssl: bool, wrac: bool) {
if ctx.args.enable_ssl { if ssl {
run_secure_listener(ctx); run_secure_listener(ctx, host, wrac);
} else { } else {
run_normal_listener(ctx); run_normal_listener(ctx, host, wrac);
} }
} }

View File

@ -16,14 +16,14 @@ pub fn accept_rac_stream(
stream.read_exact(&mut buf)?; stream.read_exact(&mut buf)?;
if buf[0] == 0x00 { if buf[0] == 0x00 {
let total_size = on_total_size(ctx.clone(), addr.clone())?; let total_size = on_total_size(ctx.clone(), addr)?;
stream.write_all(total_size.to_string().as_bytes())?; stream.write_all(total_size.to_string().as_bytes())?;
let mut id = vec![0]; let mut id = vec![0];
stream.read_exact(&mut id)?; stream.read_exact(&mut id)?;
if id[0] == 0x01 { if id[0] == 0x01 {
stream.write_all(&on_total_data(ctx.clone(), addr.clone(), Some(total_size))?)?; stream.write_all(&on_total_data(ctx.clone(), addr, Some(total_size))?)?;
} else if id[0] == 0x02 { } else if id[0] == 0x02 {
let mut buf = vec![0; 10]; let mut buf = vec![0; 10];
let size = stream.read(&mut buf)?; let size = stream.read(&mut buf)?;
@ -32,7 +32,7 @@ pub fn accept_rac_stream(
let client_has: u64 = String::from_utf8(buf)?.parse()?; let client_has: u64 = String::from_utf8(buf)?.parse()?;
stream.write_all(&on_chunked_data( stream.write_all(&on_chunked_data(
ctx.clone(), ctx.clone(),
addr.clone(), addr,
Some(total_size), Some(total_size),
client_has, client_has,
)?)?; )?)?;
@ -42,9 +42,9 @@ pub fn accept_rac_stream(
let size = stream.read(&mut buf)?; let size = stream.read(&mut buf)?;
buf.truncate(size); buf.truncate(size);
on_send_message(ctx.clone(), addr.clone(), buf)?; on_send_message(ctx.clone(), addr, buf)?;
} else if buf[0] == 0x02 { } else if buf[0] == 0x02 {
let mut buf = vec![0; ctx.args.message_limit + 2 + 512]; // FIXME: softcode this (512 = name + password) let mut buf = vec![0; ctx.args.message_limit + 2 + 512]; // TODO: softcode this (512 = name + password)
let size = stream.read(&mut buf)?; let size = stream.read(&mut buf)?;
buf.truncate(size); buf.truncate(size);
@ -62,9 +62,7 @@ pub fn accept_rac_stream(
return Ok(()); return Ok(());
}; };
if let Some(resp_id) = if let Some(resp_id) = on_send_auth_message(ctx.clone(), addr, name, password, text)? {
on_send_auth_message(ctx.clone(), addr.clone(), name, password, text)?
{
stream.write_all(&[resp_id])?; stream.write_all(&[resp_id])?;
} }
} else if buf[0] == 0x03 { } else if buf[0] == 0x03 {
@ -83,9 +81,17 @@ pub fn accept_rac_stream(
return Ok(()); return Ok(());
}; };
if let Some(resp_id) = on_register_user(ctx.clone(), addr.clone(), name, password)? { if let Some(resp_id) = on_register_user(ctx.clone(), addr, name, password)? {
stream.write_all(&[resp_id])?; stream.write_all(&[resp_id])?;
} }
} else if buf[0] == 0x69 {
let (protocol_version, name) = on_server_info(ctx.clone(), addr)?;
let mut data = Vec::new();
data.push(protocol_version);
data.append(&mut name.as_bytes().to_vec());
stream.write_all(&data)?;
} }
Ok(()) Ok(())

View File

@ -35,7 +35,7 @@ pub fn accept_wrac_stream(
if id == 0x00 { if id == 0x00 {
if data.is_empty() { if data.is_empty() {
let total_size = on_total_size(ctx.clone(), addr.clone())?; let total_size = on_total_size(ctx.clone(), addr)?;
sent_size = Some(total_size); sent_size = Some(total_size);
websocket.write(Message::Binary(Bytes::from( websocket.write(Message::Binary(Bytes::from(
@ -50,7 +50,7 @@ pub fn accept_wrac_stream(
if id == 0x01 { if id == 0x01 {
websocket.write(Message::Binary(Bytes::from(on_total_data( websocket.write(Message::Binary(Bytes::from(on_total_data(
ctx.clone(), ctx.clone(),
addr.clone(), addr,
sent_size, sent_size,
)?)))?; )?)))?;
websocket.flush()?; websocket.flush()?;
@ -58,7 +58,7 @@ pub fn accept_wrac_stream(
let client_has = String::from_utf8(data)?.parse()?; let client_has = String::from_utf8(data)?.parse()?;
websocket.write(Message::Binary(Bytes::from(on_chunked_data( websocket.write(Message::Binary(Bytes::from(on_chunked_data(
ctx.clone(), ctx.clone(),
addr.clone(), addr,
sent_size, sent_size,
client_has, client_has,
)?)))?; )?)))?;
@ -66,7 +66,7 @@ pub fn accept_wrac_stream(
} }
} }
} else if id == 0x01 { } else if id == 0x01 {
on_send_message(ctx.clone(), addr.clone(), data)?; on_send_message(ctx.clone(), addr, data)?;
} else if id == 0x02 { } else if id == 0x02 {
let msg = String::from_utf8_lossy(&data).to_string(); let msg = String::from_utf8_lossy(&data).to_string();
@ -83,7 +83,7 @@ pub fn accept_wrac_stream(
}; };
if let Some(resp_id) = if let Some(resp_id) =
on_send_auth_message(ctx.clone(), addr.clone(), name, password, text)? on_send_auth_message(ctx.clone(), addr, name, password, text)?
{ {
websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?; websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?;
websocket.flush()?; websocket.flush()?;
@ -96,15 +96,24 @@ pub fn accept_wrac_stream(
let Some(name) = segments.next() else { let Some(name) = segments.next() else {
return Ok(()); return Ok(());
}; };
let Some(password) = segments.next() else { let Some(password) = segments.next() else {
return Ok(()); return Ok(());
}; };
if let Some(resp_id) = on_register_user(ctx.clone(), addr.clone(), name, password)? if let Some(resp_id) = on_register_user(ctx.clone(), addr, name, password)? {
{
websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?; websocket.write(Message::Binary(Bytes::from(vec![resp_id])))?;
websocket.flush()?; websocket.flush()?;
} }
} else if id == 0x69 {
let (protocol_version, name) = on_server_info(ctx.clone(), addr)?;
let mut data = Vec::new();
data.push(protocol_version);
data.append(&mut name.as_bytes().to_vec());
websocket.write(Message::Binary(Bytes::from(data)))?;
websocket.flush()?;
} }
} }
} }

116
src/util.rs Normal file
View File

@ -0,0 +1,116 @@
use std::{error::Error, io::Read};
use colored::{Color, Colorize};
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
pub static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] (.*)").unwrap();
pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
pub static ref COLORED_USERNAMES: Vec<(Regex, Color)> = vec![
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), Color::Green), // bRAC
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), Color::BrightRed), // CRAB
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), Color::Magenta), // Mefidroniy
(Regex::new(r"<(.*?)> (.*)").unwrap(), Color::Cyan), // clRAC
];
pub static ref ANSI_REGEX: Regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap();
pub static ref CONTROL_CHARS_REGEX: Regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap();
}
pub fn sanitize_text(input: &str) -> String {
let without_ansi = ANSI_REGEX.replace_all(input, "");
let cleaned_text = CONTROL_CHARS_REGEX.replace_all(&without_ansi, "");
cleaned_text.into_owned()
}
pub fn format_message(enable_ip_viewing: bool, message: String) -> Option<String> {
let message = sanitize_text(&message);
let date = DATE_REGEX.captures(&message)?;
let (date, message) = (
date.get(1)?.as_str().to_string(),
date.get(2)?.as_str().to_string(),
);
let (ip, message) = if let Some(message) = IP_REGEX.captures(&message) {
(
Some(message.get(1)?.as_str().to_string()),
message.get(2)?.as_str().to_string(),
)
} else {
(None, message)
};
let message = message
.trim_start_matches("(UNREGISTERED)")
.trim_start_matches("(UNAUTHORIZED)")
.trim_start_matches("(UNAUTHENTICATED)")
.trim()
.to_string()
+ " ";
let prefix = if enable_ip_viewing {
if let Some(ip) = ip {
format!(
"{}{} [{}]",
ip,
" ".repeat(if 15 >= ip.chars().count() {
15 - ip.chars().count()
} else {
0
}),
date
)
} else {
format!("{} [{}]", " ".repeat(15), date)
}
} else {
format!("[{}]", date)
};
Some(if let Some(captures) = find_username_color(&message) {
let nick = captures.0;
let content = captures.1;
let color = captures.2;
format!(
"{} {} {}",
prefix.white().dimmed(),
format!("<{}>", nick).color(color).bold(),
content.white().blink()
)
} else {
format!("{} {}", prefix.white().dimmed(), message.white().blink())
})
}
pub fn find_username_color(message: &str) -> Option<(String, String, Color)> {
for (re, color) in COLORED_USERNAMES.iter() {
if let Some(captures) = re.captures(message) {
return Some((
captures[1].to_string(),
captures[2].to_string(),
color.clone(),
));
}
}
None
}
pub fn read_u32<R>(cursor: &mut R) -> Result<u32, Box<dyn Error>>
where
R: Read,
{
let mut buf = [0; 4];
cursor.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
pub fn read_string<R>(cursor: &mut R, len: usize) -> Result<String, Box<dyn Error>>
where
R: Read,
{
let mut buf = vec![0; len];
cursor.read_exact(&mut buf)?;
String::from_utf8(buf).map_err(|e| e.into())
}