From 952908292b6f1d71110ea82ee39e99d65524d947 Mon Sep 17 00:00:00 2001
From: Compositr <compositr@compositr.dev>
Date: Wed, 30 Oct 2024 13:49:27 +1100
Subject: [PATCH] feat: URL handling

---
 src/http/requests.rs | 42 ++++++++++++++++++++++++++++++++++--------
 src/main.rs          | 20 ++++++++++++++------
 2 files changed, 48 insertions(+), 14 deletions(-)

diff --git a/src/http/requests.rs b/src/http/requests.rs
index 2e0258b..6111a3e 100644
--- a/src/http/requests.rs
+++ b/src/http/requests.rs
@@ -1,12 +1,38 @@
 use std::collections::HashMap;
 
-struct Path<'a> {
-  pub query: HashMap<&'a str, &'a str>
+pub struct URL<'a> {
+    pub raw: &'a str,
+    pub path: &'a str,
+    pub query: HashMap<&'a str, &'a str>,
 }
 
-impl Path {
-  pub fn new<'a>(path: &'a str) -> Self {
-    let query = path.split("?").collect();
-    Path { query }
-  }
-}
\ No newline at end of file
+impl<'a> URL<'a> {
+    pub fn new(url: &'a str) -> Option<Self> {
+        let raw = url;
+
+        // Query string parsing
+        let mut query = HashMap::new();
+        let mut split = url.split('?');
+        let path = split.nth(0)?;
+        let query_string = split.next();
+
+        for pair in query_string.unwrap_or("").split('&') {
+            let mut key_value = pair.split('=');
+            let key = match key_value.next() {
+                Some(key) => key,
+                None => continue,
+            };
+            let value = key_value.next().unwrap_or("");
+            query.insert(key, value);
+        }
+
+        // Drop trailing slash but not for root path
+        let path = if path.ends_with("/") && path.len() > 1 {
+            &path[..path.len() - 1]
+        } else {
+            path
+        };
+
+        Some(URL { raw, path, query })
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index 1734f9e..9322b4c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,6 +5,7 @@ use std::{
 };
 
 use ctrlc;
+use http::requests::URL;
 
 mod http;
 
@@ -49,19 +50,26 @@ fn handle_connection(mut stream: TcpStream) {
         return;
     }
 
-    let (method, path) = (request_line_parts[0], request_line_parts[1]);
+    let (method, url_str) = (request_line_parts[0], request_line_parts[1]);
     if method != "GET" {
         http::responses::send_405(stream).unwrap_or_else(|_| error("Failed to send 405 response"));
         return;
     }
 
-    let path = if path.ends_with("/") {
-        &path[..path.len() - 1]
-    } else {
-        path
+    let url = match http::requests::URL::new(url_str) {
+        Some(path) => path,
+        None => {
+            http::responses::send_400(stream)
+                .unwrap_or_else(|_| error("Failed to send 400 response"));
+            return;
+        }
     };
 
-    match path {
+    handle_request(url, stream);
+}
+
+fn handle_request(url: URL, stream: TcpStream) {
+    match url.path {
         "/" => http::responses::ok(stream, "OK - luciders is working!")
             .unwrap_or_else(|_| error("Failed to send 200 response")),
         _ => http::responses::send_404(stream)