Rain 9 kuukautta sitten
vanhempi
sitoutus
e14d4805b9
6 muutettua tiedostoa jossa 236 lisäystä ja 64 poistoa
  1. 8 2
      client/src/main.rs
  2. 0 2
      server/Cargo.toml
  3. 37 59
      server/src/main.rs
  4. 9 1
      utils/src/aes.rs
  5. 181 0
      utils/src/http.rs
  6. 1 0
      utils/src/lib.rs

+ 8 - 2
client/src/main.rs

@@ -26,6 +26,7 @@ const MAX_MESSAGE_LENGTH: usize = 512;
 const MAX_USERNAME_LENGTH: usize = 16;
 const DEFAULT_USERNAME_PREFIX: &str = "Myst";
 const DEFAULT_CHANNEL: &str = "Root";
+const DEFAULT_PASSWORD: &str = "null";
 
 const SAVE_FILE: &str = "savedata.bin";
 const SAVE_FILE_FUZZY: u64 = 0b0110110001101001011001110110110101100001001000000101100001000100;
@@ -69,7 +70,7 @@ impl Appdata {
             blocked_phrases_censor_char: '*',
             current_channel: DEFAULT_CHANNEL.to_string(),
             local_channels: vec![DEFAULT_CHANNEL.to_string()],
-            api_password: String::new(),
+            api_password: DEFAULT_PASSWORD.to_string(),
         }
     }
 }
@@ -531,7 +532,12 @@ fn main() {
 
                     siv.with_user_data(|appdata: &mut Appdata| {
                         appdata.api_endpoint = input_addr.to_string();
-                        appdata.api_password = input_password.to_string();
+
+                        let mut password = input_password.to_string();
+                        if password.is_empty() {
+                            password = DEFAULT_PASSWORD.to_string();
+                        }
+                        appdata.api_password = password;
 
                         if let Ok(rate) = input_refresh.parse() {
                             appdata.api_refresh_rate = rate;

+ 0 - 2
server/Cargo.toml

@@ -5,5 +5,3 @@ edition = "2021"
 
 [dependencies]
 utils = { path = "../utils" }
-
-actix-web = "4"

+ 37 - 59
server/src/main.rs

@@ -1,56 +1,41 @@
-use std::sync::Mutex;
-
-use actix_web::{dev::Service as _, get, post, web, HttpResponse, HttpServer, Responder};
-use utils::strings::StaticString;
+use utils::{
+    http::{self, Request, Response},
+    strings::StaticString,
+};
 
 const MAX_MESSAGE_LENGTH: usize = 2048;
 const MESSAGE_HISTORY_LENGTH: usize = 64;
 
 #[derive(Debug)]
-struct AppdataInner {
+struct Appdata {
     messages: [StaticString<MAX_MESSAGE_LENGTH>; MESSAGE_HISTORY_LENGTH],
     index: usize,
 }
 
-impl AppdataInner {
+impl Appdata {
     pub fn new() -> Self {
         Self {
             messages: core::array::from_fn(|_| StaticString::new()),
             index: 0,
         }
     }
-}
-
-#[derive(Debug)]
-struct Appdata {
-    inner: Mutex<AppdataInner>,
-}
-
-impl Appdata {
-    pub fn new() -> Self {
-        Self {
-            inner: Mutex::new(AppdataInner::new()),
-        }
-    }
 
-    pub fn insert_message(&self, message: String) -> Result<(), ()> {
+    pub fn insert_message(&mut self, message: String) -> Result<(), ()> {
         if message.len() > MAX_MESSAGE_LENGTH {
             return Err(());
         }
 
-        let mut guard = self.inner.lock().unwrap();
-        let index = guard.index;
-        guard.messages[index] = message.into();
-        guard.index = (index + 1) % guard.messages.len();
+        let index = self.index;
+        self.messages[index] = message.into();
+        self.index = (index + 1) % self.messages.len();
 
         Ok(())
     }
 
     pub fn get_messages(&self) -> Vec<String> {
-        let guard = self.inner.lock().unwrap();
         let mut messages = vec![];
 
-        for message in guard.messages.iter() {
+        for message in self.messages.iter() {
             if message.is_empty() {
                 continue;
             }
@@ -61,47 +46,40 @@ impl Appdata {
     }
 }
 
-#[get("/")]
-async fn index_get(data: web::Data<Appdata>) -> impl Responder {
-    let string = data.get_messages().join(",");
+fn index_get_sync(state: &mut Appdata, _: Request) -> Response {
+    let string = state.get_messages().join(",");
     println!("Sending `{}` characters", string.len());
-    HttpResponse::Ok().body(string)
+    Response::ok().body(&string)
 }
 
-#[post("/")]
-async fn index_post(body: web::Payload, data: web::Data<Appdata>) -> impl Responder {
-    let bytes = body.to_bytes().await.unwrap();
-    let str = String::from_utf8_lossy(&bytes).into_owned();
+fn index_post_sync(state: &mut Appdata, request: Request) -> Response {
+    let body = request.body;
+
+    println!("Received ({}): {:#?}", body.len(), body);
 
-    println!("Received ({}): {:#?}", str.len(), str);
+    match state.insert_message(body.clone()) {
+        Ok(()) => Response::ok().body(&body),
+        Err(()) => Response::bad_request(),
+    }
+}
 
-    match data.insert_message(str.clone()) {
-        Ok(()) => HttpResponse::Ok().body(str),
-        Err(()) => HttpResponse::BadRequest().body(""),
+fn router(state: &mut Appdata, request: Request) -> Response {
+    if request.url != "/" {
+        return Response::not_found();
+    }
+
+    match request.method {
+        http::Method::Get => index_get_sync(state, request),
+        http::Method::Post => index_post_sync(state, request),
+        http::Method::Other => Response::method_not_allowed(),
     }
 }
 
-#[actix_web::main]
-async fn main() -> std::io::Result<()> {
+fn main() -> http::Result<()> {
     println!("Starting...");
 
-    let appdata = web::Data::new(Appdata::new());
-
-    HttpServer::new(move || {
-        actix_web::App::new()
-            .app_data(appdata.clone())
-            .wrap_fn(|req, srv| {
-                // println!("{:#?}", LoggedRequest::new(&req));
-                let fut = srv.call(req);
-                async {
-                    let res = fut.await?;
-                    Ok(res)
-                }
-            })
-            .service(index_get)
-            .service(index_post)
-    })
-    .bind(("127.0.0.1", 8080))?
-    .run()
-    .await
+    let appdata = Appdata::new();
+    http::run("127.0.0.1:8080", appdata, router)?;
+
+    Ok(())
 }

+ 9 - 1
utils/src/aes.rs

@@ -588,7 +588,7 @@ mod test {
     }
 
     #[test]
-    fn test_empty() {
+    fn test_empty_indexing() {
         encrypt_cbc(&mut vec![], &"");
         let _ = decrypt_cbc(&mut vec![], &"");
         let _ = encrypt_block(&mut empty_block(), &[]);
@@ -596,4 +596,12 @@ mod test {
         pad(&mut vec![]);
         let _ = unpad(&mut vec![]);
     }
+
+    #[test]
+    fn test_empty_password() {
+        let mut input = TEST_ARRAY_16.to_vec();
+        encrypt_cbc(&mut input, &"");
+        decrypt_cbc(&mut input, &"").unwrap();
+        assert_eq!(input, TEST_ARRAY_16);
+    }
 }

+ 181 - 0
utils/src/http.rs

@@ -0,0 +1,181 @@
+use std::{
+    collections::HashMap,
+    io::{Read as _, Write},
+    net::TcpListener,
+};
+
+const VALID_HTTP_VERSIONS: &[&str] = &["HTTP/1.0", "HTTP/1.1"];
+const HTTP_VERSION: &str = "HTTP/1.1";
+const MAX_REQUEST_SIZE: usize = 512;
+
+pub type Result<T> = std::result::Result<T, HttpError>;
+
+#[derive(Debug)]
+pub enum HttpError {
+    InvalidRequest,
+    TcpIoError(std::io::Error),
+}
+
+impl From<std::io::Error> for HttpError {
+    fn from(value: std::io::Error) -> Self {
+        Self::TcpIoError(value)
+    }
+}
+
+pub fn run<S, F>(address: &str, mut state: S, callback: F) -> Result<()>
+where
+    F: Fn(&mut S, Request) -> Response,
+{
+    let listener = TcpListener::bind(address)?;
+
+    for mut stream in listener.incoming().flatten() {
+        let mut buf = [0; MAX_REQUEST_SIZE];
+        let Ok(n) = stream.read(&mut buf) else {
+            continue;
+        };
+        let request_string = String::from_utf8_lossy(&buf[..n]);
+        let Ok(request) = Request::try_from(request_string.to_string()) else {
+            continue;
+        };
+
+        let response = callback(&mut state, request);
+        stream.write_all(response.to_string().as_bytes())?;
+        stream.flush()?;
+        stream.shutdown(std::net::Shutdown::Both)?;
+    }
+
+    Ok(())
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Method {
+    Get,
+    Post,
+    Other,
+}
+
+#[derive(Debug)]
+pub struct Request {
+    pub method: Method,
+    pub url: String,
+    pub headers: HashMap<String, String>,
+    pub body: String,
+}
+
+impl TryFrom<String> for Request {
+    type Error = HttpError;
+
+    fn try_from(value: String) -> Result<Self> {
+        const E: HttpError = HttpError::InvalidRequest;
+
+        let Some((top, body)) = value.split_once("\r\n\r\n") else {
+            return Err(E);
+        };
+
+        let mut top = top.split("\r\n");
+        let Some(info) = top.next() else {
+            return Err(E);
+        };
+        let headers = top.collect::<Vec<&str>>();
+
+        let mut info = info.split(" ");
+        let Some(method) = info.next() else {
+            return Err(E);
+        };
+
+        let Some(url) = info.next() else {
+            return Err(E);
+        };
+
+        let Some(version) = info.next() else {
+            return Err(E);
+        };
+
+        if !VALID_HTTP_VERSIONS.contains(&version) {
+            return Err(E);
+        }
+
+        let method = match &method.to_uppercase()[..] {
+            "GET" => Method::Get,
+            "POST" => Method::Post,
+            _ => Method::Other,
+        };
+
+        let mut headers_map = HashMap::new();
+        for h in headers {
+            let Some((key, value)) = h.split_once(":") else {
+                continue;
+            };
+
+            headers_map.insert(key.trim().to_lowercase(), value.trim().to_string());
+        }
+
+        Ok(Self {
+            method,
+            url: url.to_string(),
+            headers: headers_map,
+            body: body.to_string(),
+        })
+    }
+}
+
+#[derive(Debug)]
+pub struct Response {
+    pub status_code: u16,
+    pub headers: HashMap<String, String>,
+    pub body: String,
+}
+
+impl Response {
+    pub fn new(code: u16) -> Self {
+        Self {
+            status_code: code,
+            headers: HashMap::new(),
+            body: String::new(),
+        }
+    }
+
+    pub fn ok() -> Self {
+        Self::new(200)
+    }
+
+    pub fn bad_request() -> Self {
+        Self::new(400)
+    }
+
+    pub fn not_found() -> Self {
+        Self::new(404)
+    }
+
+    pub fn method_not_allowed() -> Self {
+        Self::new(405)
+    }
+
+    pub fn header(mut self, key: &str, value: &str) -> Self {
+        self.headers.insert(key.to_lowercase(), value.to_string());
+        self
+    }
+
+    pub fn body(mut self, body: &str) -> Self {
+        self.body = body.to_string();
+        self
+    }
+}
+
+impl ToString for Response {
+    fn to_string(&self) -> String {
+        let mut s = format!("{} {} \r\n", HTTP_VERSION, self.status_code.to_string());
+
+        for (idx, (key, value)) in self.headers.iter().enumerate() {
+            s.push_str(&format!("{}: {}", key, value));
+            if idx + 1 < self.headers.len() {
+                s.push('\n');
+            }
+        }
+
+        s.push_str("\r\n");
+        s.push_str(&self.body);
+
+        s
+    }
+}

+ 1 - 0
utils/src/lib.rs

@@ -1,6 +1,7 @@
 pub mod aes;
 pub mod binary;
 pub mod hash;
+pub mod http;
 pub mod rng;
 pub mod serialize;
 pub mod strings;