From 94752493d1a03d7c7c3172a53279855e9c99203d Mon Sep 17 00:00:00 2001
From: Darius Auding <Darius.auding@gmx.de>
Date: Fri, 30 Jun 2023 19:23:38 +0200
Subject: [PATCH] Add write and build function to result, add basis for cookie
 setting possibility, divide the response module;

---
 core/http/src/handlers/handler.rs             | 69 +++++++++----------
 core/http/src/handling/request/form_utils.rs  |  6 ++
 core/http/src/handling/request/mod.rs         |  2 +-
 core/http/src/handling/response/cookie.rs     |  0
 core/http/src/handling/response/datatypes.rs  | 45 ++++++++++++
 core/http/src/handling/response/mod.rs        | 12 ++++
 core/http/src/handling/response/response.rs   | 32 +++++++++
 .../{response.rs => response/status.rs}       | 66 ------------------
 core/http/src/handling/response/traits.rs     | 48 +++++++++++++
 site/src/main.rs                              | 20 ++----
 10 files changed, 185 insertions(+), 115 deletions(-)
 create mode 100644 core/http/src/handling/response/cookie.rs
 create mode 100644 core/http/src/handling/response/datatypes.rs
 create mode 100644 core/http/src/handling/response/mod.rs
 create mode 100644 core/http/src/handling/response/response.rs
 rename core/http/src/handling/{response.rs => response/status.rs} (84%)
 create mode 100644 core/http/src/handling/response/traits.rs

diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs
index 9596f52..3c46159 100644
--- a/core/http/src/handlers/handler.rs
+++ b/core/http/src/handlers/handler.rs
@@ -1,12 +1,12 @@
-use std::path::PathBuf;
+use std::{path::PathBuf, io};
 
-use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt, AsyncWriteExt}, net::TcpStream};
+use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt}, net::TcpStream};
 
 use crate::handling::{
     file_handlers::NamedFile,
     request::Request,
     response::{Outcome, Response, ResponseBody, Status},
-    routes::Data, methods::Method,
+    routes::{Data, Body}, methods::Method,
 };
 use crate::setup::MountPoint;
 
@@ -78,13 +78,17 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin
                 size
             } else {
                 eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method);
-                len_not_defined(stream, Status::LengthRequired).await;
+                if let Err(e) = len_not_defined(stream, Status::LengthRequired).await {
+                    error_occured_when_writing(e)
+                };
                 return;
             }
         } else {
             if request.mandatory_body() {
                 eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method);
-                len_not_defined(stream, Status::LengthRequired).await;
+                if let Err(e) = len_not_defined(stream, Status::LengthRequired).await {
+                    error_occured_when_writing(e)
+                };
                 return;
             }
             0
@@ -94,7 +98,9 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin
             let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()];
             let read = buf_reader.read(&mut buffer).await.unwrap();
             if read != length {
-                len_not_defined(stream, Status::LengthRequired).await;
+                if let Err(e) = len_not_defined(stream, Status::LengthRequired).await {
+                    error_occured_when_writing(e)
+                };
                 return;
             }
             data.is_complete = true;
@@ -133,47 +139,40 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin
 
     let response = match handled_response {
         Some(val) => match val {
-            Outcome::Success(success) => (
-                format!("HTTP/1.1 {}\r\n", success.status.unwrap_or(Status::Ok))
-                    + &success.headers.join("\r\n")
-                    + "\r\n\r\n",
-                if request.method == Method::Head {
-                    vec![]
-                } else {
-                    success.body.get_data()
-                }
-            ),
+            Outcome::Success(success) => success
+            ,
             Outcome::Failure(error) => failure_handler(error),
             Outcome::Forward(_) => failure_handler(Status::NotFound),
         },
         None => failure_handler(Status::NotFound),
     };
 
-    let mut resp = response.0.as_bytes().to_vec();
-    resp.extend_from_slice(&response.1);
-    if let Err(e) = stream.write_all(&resp).await {
+    if let Err(e) = response.write(stream, Some(request)).await {
         eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
     } 
 }
 
-fn failure_handler(status: Status) -> (String, Vec<u8>) {
+fn failure_handler<'a>(status: Status) -> Response<'a> {
     let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap();
-    (
-        format!(
-            "HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n",
-            status,
-            page_404.get_len(),
-            page_404.get_mime()
-        ),
-        page_404.get_data(),
-    )
+    Response {
+        cookies: None,
+        headers: vec![],
+        status: Some(status),
+        body: Box::new(Body::new(page_404.get_data(), page_404.get_mime())),
+    }
 }
 
-async fn len_not_defined(mut stream: TcpStream, status: Status) {
+async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()>{
     let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap();
-    let mut response = format!("HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", status, page_411.get_len(), page_411.get_mime()).as_bytes().to_vec();
-    response.extend_from_slice(&page_411.get_data());
-        if let Err(e) = stream.write_all(&response).await {
-            eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
-        }
+    Response {
+        cookies: None,
+        headers: vec![],
+        status: Some(status),
+        body: Box::new(Body::new(page_411.get_data(), page_411.get_mime())),
+    }.write(stream, None).await?;
+    Ok(())
+}
+
+fn error_occured_when_writing(e: io::Error) {
+    eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
 }
diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs
index d70b2bc..cab16eb 100644
--- a/core/http/src/handling/request/form_utils.rs
+++ b/core/http/src/handling/request/form_utils.rs
@@ -17,6 +17,12 @@ impl Request<'_> {
     ///
     /// # Examples
     /// ```
+    /// use http::handling::request::Request;
+    /// use http::handling::request::ParseFormError;
+    /// use http::handling::request::ParseErrors;
+    /// use http::handling::methods::Method;
+    ///
+    ///
     /// let request = Request {
     ///     uri: "/form?name=Name&age=Age",
     ///     headers: vec![],
diff --git a/core/http/src/handling/request/mod.rs b/core/http/src/handling/request/mod.rs
index 54da4ad..f04c57a 100644
--- a/core/http/src/handling/request/mod.rs
+++ b/core/http/src/handling/request/mod.rs
@@ -3,4 +3,4 @@ mod datatypes;
 mod form_utils;
 mod request_impl;
 mod request_mime;
-pub use datatypes::{MediaType, ParseFormError, Request};
+pub use datatypes::{MediaType, ParseErrors, ParseFormError, Request};
diff --git a/core/http/src/handling/response/cookie.rs b/core/http/src/handling/response/cookie.rs
new file mode 100644
index 0000000..e69de29
diff --git a/core/http/src/handling/response/datatypes.rs b/core/http/src/handling/response/datatypes.rs
new file mode 100644
index 0000000..503929a
--- /dev/null
+++ b/core/http/src/handling/response/datatypes.rs
@@ -0,0 +1,45 @@
+use std::time::Duration;
+
+use super::{ResponseBody, Status};
+
+type HeaderMap = Vec<String>;
+
+#[derive(Debug)]
+pub enum Outcome<S, E, F> {
+    Success(S),
+    Failure(E),
+    Forward(F),
+}
+
+pub enum SameSite {
+    None,
+    Lax,
+    Strict,
+}
+
+pub struct Cookie<'a> {
+    /// Storage for the cookie string. Only used if this structure was derived
+    /// from a string that was subsequently parsed.
+    cookie_string: &'a str,
+    name: &'a str,
+    value: &'a str,
+    // expires: Option<Tm>,
+    max_age: Option<Duration>,
+    /// The cookie's domain, if any.
+    domain: Option<&'a str>,
+    /// The cookie's path domain, if any.
+    path: Option<&'a str>,
+    /// Whether this cookie was marked Secure.
+    secure: Option<bool>,
+    /// Whether this cookie was marked HttpOnly.
+    http_only: Option<bool>,
+    /// The draft `SameSite` attribute.
+    same_site: Option<SameSite>,
+}
+
+pub struct Response<'a> {
+    pub headers: HeaderMap,
+    pub cookies: Option<Cookie<'a>>,
+    pub status: Option<Status>,
+    pub body: Box<dyn ResponseBody>,
+}
diff --git a/core/http/src/handling/response/mod.rs b/core/http/src/handling/response/mod.rs
new file mode 100644
index 0000000..017bc15
--- /dev/null
+++ b/core/http/src/handling/response/mod.rs
@@ -0,0 +1,12 @@
+mod cookie;
+mod datatypes;
+mod response;
+mod status;
+mod traits;
+
+pub use datatypes::Cookie;
+pub use datatypes::Outcome;
+pub use datatypes::Response;
+pub use datatypes::SameSite;
+pub use status::Status;
+pub use traits::ResponseBody;
diff --git a/core/http/src/handling/response/response.rs b/core/http/src/handling/response/response.rs
new file mode 100644
index 0000000..dfdb909
--- /dev/null
+++ b/core/http/src/handling/response/response.rs
@@ -0,0 +1,32 @@
+use std::io::Result;
+
+use tokio::{net::TcpStream, io::AsyncWriteExt};
+
+use crate::handling::{methods::Method, request::Request, response::Status};
+
+use super::Response;
+
+impl Response<'_> {
+    pub fn build(self, request: Option<Request>) -> Vec<u8> {
+        let mut compiled_headers = format!("HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n", self.status.unwrap_or(Status::Ok), self.body.get_len(), self.body.get_mime())
+            + &self.headers.join("\r\n")
+            + "\r\n";
+        let is_head = if let Some(req) = request {
+            req.method == Method::Head
+        } else {
+            false
+        };
+        let compiled_body = if is_head {
+            vec![]
+        } else {
+            self.body.get_data()
+        };
+        let compiled_out = unsafe { compiled_headers.as_mut_vec() };
+        compiled_out.extend_from_slice(&compiled_body);
+        compiled_out.to_vec()
+    }
+    pub async fn write(self, mut stream: TcpStream, request: Option<Request<'_>>) -> Result<()> {
+        let resp = self.build(request);
+        stream.write_all(&resp).await?;
+        Ok(())
+}}
diff --git a/core/http/src/handling/response.rs b/core/http/src/handling/response/status.rs
similarity index 84%
rename from core/http/src/handling/response.rs
rename to core/http/src/handling/response/status.rs
index 81a54af..9f825af 100644
--- a/core/http/src/handling/response.rs
+++ b/core/http/src/handling/response/status.rs
@@ -1,71 +1,5 @@
 use std::fmt::Display;
 
-use crate::utils::mime::mime_enum::Mime;
-
-use super::routes::Body;
-
-type HeaderMap = Vec<String>;
-
-#[derive(Debug)]
-pub enum Outcome<S, E, F> {
-    Success(S),
-    Failure(E),
-    Forward(F),
-}
-
-pub trait ResponseBody: Send {
-    fn get_data(&self) -> Vec<u8>;
-    fn get_mime(&self) -> Mime;
-    fn get_len(&self) -> usize;
-}
-
-impl ResponseBody for Body {
-    fn get_data(&self) -> Vec<u8> {
-        self.body()
-    }
-    fn get_mime(&self) -> Mime {
-        self.mime_type()
-    }
-
-    fn get_len(&self) -> usize {
-        self.get_data().len()
-    }
-}
-
-impl ResponseBody for &str {
-    fn get_data(&self) -> Vec<u8> {
-        self.as_bytes().to_vec()
-    }
-
-    fn get_mime(&self) -> Mime {
-        Mime::TextPlain
-    }
-
-    fn get_len(&self) -> usize {
-        self.len()
-    }
-}
-
-impl ResponseBody for String {
-    fn get_data(&self) -> Vec<u8> {
-        self.as_bytes().to_vec()
-    }
-
-    fn get_mime(&self) -> Mime {
-        Mime::TextPlain
-    }
-
-    fn get_len(&self) -> usize {
-        self.len()
-    }
-}
-
-pub struct Response {
-    pub headers: HeaderMap,
-    pub status: Option<Status>,
-    pub body: Box<dyn ResponseBody>,
-}
-
 #[derive(Debug)]
 pub enum Status {
     Continue,
diff --git a/core/http/src/handling/response/traits.rs b/core/http/src/handling/response/traits.rs
new file mode 100644
index 0000000..96ac87c
--- /dev/null
+++ b/core/http/src/handling/response/traits.rs
@@ -0,0 +1,48 @@
+use crate::{handling::routes::Body, utils::mime::mime_enum::Mime};
+
+pub trait ResponseBody: Send {
+    fn get_data(&self) -> Vec<u8>;
+    fn get_mime(&self) -> Mime;
+    fn get_len(&self) -> usize;
+}
+
+impl ResponseBody for Body {
+    fn get_data(&self) -> Vec<u8> {
+        self.body()
+    }
+    fn get_mime(&self) -> Mime {
+        self.mime_type()
+    }
+
+    fn get_len(&self) -> usize {
+        self.get_data().len()
+    }
+}
+
+impl ResponseBody for &str {
+    fn get_data(&self) -> Vec<u8> {
+        self.as_bytes().to_vec()
+    }
+
+    fn get_mime(&self) -> Mime {
+        Mime::TextPlain
+    }
+
+    fn get_len(&self) -> usize {
+        self.len()
+    }
+}
+
+impl ResponseBody for String {
+    fn get_data(&self) -> Vec<u8> {
+        self.as_bytes().to_vec()
+    }
+
+    fn get_mime(&self) -> Mime {
+        Mime::TextPlain
+    }
+
+    fn get_len(&self) -> usize {
+        self.len()
+    }
+}
diff --git a/site/src/main.rs b/site/src/main.rs
index f1fda07..f677a34 100644
--- a/site/src/main.rs
+++ b/site/src/main.rs
@@ -4,7 +4,7 @@ use http::handling::{
     file_handlers::NamedFile,
     methods::Method,
     request::{Request, ParseFormError},
-    response::{Outcome, Response, ResponseBody, Status},
+    response::{Outcome, Response, Status},
     routes::{Data, Route},
 };
 
@@ -28,10 +28,8 @@ fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Statu
     };
     let response = hashmap_to_string(&keys);
     Outcome::Success(Response {
-        headers: vec![
-            format!("Content-Length: {}", response.len()),
-            format!("Content-Type: text/plain"),
-        ],
+        headers: vec![],
+        cookies: None,
         status: Some(Status::Ok),
         body: Box::new(response),
     })
@@ -42,10 +40,8 @@ fn handler(request: Request<'_>, _data: Data) -> Outcome<Response, Status, Data>
     let response = fileserver(request.uri.strip_prefix("static/").unwrap());
     let response = match response {
         Ok(dat) => Response {
-            headers: vec![
-                format!("Content-Length: {}", dat.get_len()),
-                format!("Content-Type: {}", dat.get_mime()),
-            ],
+            headers: vec![],
+            cookies: None,
             status: Some(Status::Ok),
             body: Box::new(dat),
         },
@@ -69,10 +65,8 @@ fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Da
         return Outcome::Failure(Status::BadRequest);
     };
     Outcome::Success(Response {
-        headers: vec![
-            format!("Content-Length: {}", dat.len()),
-            format!("Content-Type: text/plain"),
-        ],
+        headers: vec![],
+        cookies: None,
         status: Some(Status::Ok),
         body: Box::new(dat),
     })
-- 
GitLab