Skip to content
Snippets Groups Projects
Commit 29bec6fc authored by codecraft's avatar codecraft :crocodile:
Browse files

Refactoring the Request struct into it's own seperated module with

seprate impl's, fixing some code, ...
parent 3a080227
No related branches found
No related tags found
1 merge request!1Initial feature merge
......@@ -4,7 +4,7 @@ use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt, AsyncWriteExt}, net::
use crate::handling::{
file_handlers::NamedFile,
request::{Request, extract_cookies_from_vec},
request::Request,
response::{Outcome, Response, ResponseBody, Status},
routes::Data, methods::Method,
};
......@@ -26,7 +26,6 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin
}
http_request.push(buffer);
}
println!("{:?}", http_request);
let request_status_line = if let Some(status_line) = http_request.get(0).cloned() {
status_line
......@@ -35,15 +34,16 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin
return;
};
let request = Request {
let mut request = Request {
uri: if let Some(uri) = &request_status_line.split(" ").nth(1) {
*uri
} else {
eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m");
return;
},
cookies: extract_cookies_from_vec(&mut http_request),
cookies: Request::extract_cookies_from_vec(&mut http_request),
headers: http_request,
mime_type: None,
method: if let Some(method) = request_status_line
.split(" ")
.next() {
......@@ -90,6 +90,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin
0
};
if length != 0 {
request.mime_type = Request::extract_mime_from_vec(&mut request.headers);
let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()];
let read = buf_reader.read(&mut buffer).await.unwrap();
if read != length {
......@@ -152,9 +153,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin
resp.extend_from_slice(&response.1);
if let Err(e) = stream.write_all(&resp).await {
eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
return;
}
return;
}
}
fn failure_handler(status: Status) -> (String, Vec<u8>) {
......
pub mod handlers;
pub mod handler;
use std::collections::HashMap;
pub fn extract_cookies_from_vec(headers: &mut Vec<String>) -> Option<HashMap<String, String>> {
let mut cookies: HashMap<String, String> = HashMap::new();
let mut cookies_string = if let Some(index) = headers
.iter()
.position(|header| header.starts_with("Cookie: "))
{
headers.remove(index)
} else {
return None;
};
cookies_string = cookies_string
.strip_prefix("Cookie: ")
.unwrap()
.trim()
.to_string();
for cookie in cookies_string.split(';') {
let Some((name, cookie)) = cookie.split_once('=') else {
use super::Request;
impl Request<'_> {
pub fn extract_cookies_from_vec(headers: &mut Vec<String>) -> Option<HashMap<String, String>> {
let mut cookies: HashMap<String, String> = HashMap::new();
let mut cookies_string = if let Some(index) = headers
.iter()
.position(|header| header.starts_with("Cookie: "))
{
headers.remove(index)
} else {
return None;
};
cookies
.entry(name.trim().to_string())
.or_insert(cookie.trim().to_string());
cookies_string = cookies_string
.strip_prefix("Cookie: ")
.unwrap()
.trim()
.to_string();
for cookie in cookies_string.split(';') {
let Some((name, cookie)) = cookie.split_once('=') else {
return None;
};
cookies
.entry(name.trim().to_string())
.or_insert(cookie.trim().to_string());
}
Some(cookies)
}
Some(cookies)
}
#[cfg(test)]
mod test {
use crate::handling::request::extract_cookies_from_vec;
use crate::handling::request::Request;
#[test]
fn test_cookies() {
......@@ -46,8 +50,8 @@ mod test {
let mut wrong = vec!["GET / HTTP/1.1".to_string()];
assert_eq!(None, extract_cookies_from_vec(&mut wrong));
let cookies = extract_cookies_from_vec(&mut request_vec);
assert_eq!(None, Request::extract_cookies_from_vec(&mut wrong));
let cookies = Request::extract_cookies_from_vec(&mut request_vec);
assert_eq!(right_finished_vec, request_vec);
assert_eq!("23", cookies.clone().unwrap().get("io").unwrap());
assert_eq!("as", cookies.unwrap().get("f").unwrap());
......
use std::{collections::HashMap, error::Error, fmt::Display};
use crate::handling::{methods::Method, routes::Uri};
use crate::{
handling::{methods::Method, routes::Uri},
utils::mime::mime_enum::Mime,
};
pub trait FromRequest: Send {
fn get_data(&self) -> &Self;
......@@ -29,6 +32,7 @@ pub struct Request<'a> {
pub headers: HeaderMap,
pub method: Method,
pub cookies: Option<HashMap<String, String>>,
pub mime_type: Option<Mime>,
// pub connection: ConnectionMeta,
}
......
use std::collections::HashMap;
use crate::{handling::routes::Data, utils::mime::mime_enum::Mime};
use super::{datatypes::ParseErrors, ParseFormError, Request};
static TWO_NEWLINES: u8 = 3;
impl Request<'_> {
pub fn get_get_form_keys<'a>(
&'a self,
keys: &'a [&str],
) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> {
let data = if let Some(val) = self.uri.split_once('?') {
val
} else {
return Err(ParseFormError {
error: ParseErrors::NoData,
});
};
let data = data
.1
.split("&")
.map(|kvp| kvp.split_once("="))
.collect::<Vec<Option<(&str, &str)>>>();
let mut values: HashMap<&str, &str> = HashMap::new();
for kvp in data {
let kvp = if let Some(kvp) = kvp {
kvp
} else {
continue;
};
values.insert(kvp.0, kvp.1);
}
let mut response = HashMap::new();
for key in keys {
let entry = if let Some(val) = values.get(key) {
Ok(*val)
} else {
Err(ParseFormError {
error: ParseErrors::NoData,
})
};
response.insert((*key).into(), entry);
}
Ok(response)
}
pub fn get_post_data<'a>(
&'a self,
keys: &[&'a str],
data: &Data,
) -> Result<HashMap<String, Result<Vec<u8>, ParseFormError>>, ParseFormError> {
let data = data.buffer.as_slice();
let mut keymap: HashMap<String, Result<Vec<u8>, ParseFormError>> =
HashMap::with_capacity(keys.len());
for key in keys {
keymap.entry(key.to_string()).or_insert(Err(ParseFormError {
error: ParseErrors::NoData,
}));
}
let Some(mime_type) = self.mime_type else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
match mime_type {
Mime::ApplicationXWwwFormUrlencoded => {
let Ok(data) = String::from_utf8(data.to_vec()) else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
for kvp in data.split('&') {
let Some(mut kvp) = kvp.split_once('=') else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let Some(thing) = keymap.get_mut(kvp.0) else {
continue;
};
kvp.1 = kvp.1.trim_end_matches('\0');
*thing = Ok(kvp.1.as_bytes().to_vec());
}
}
Mime::MultipartFormData => {
let Some(post_type) = self
.headers
.iter()
.find(|header| header.contains("Content-Type: ")) else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let content_type = post_type.trim().strip_prefix("Content-Type: ").unwrap();
let Some(mut boundary) = content_type.split(';').find(|element| element.trim().starts_with("boundary=")) else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
boundary = boundary
.trim()
.strip_prefix("boundary=")
.unwrap()
.trim_matches('"');
let mut temp_bound = "--".to_string();
temp_bound.push_str(&format!("{boundary}"));
let end_boundary = format!("{temp_bound}--\r").as_bytes().to_owned();
temp_bound.push('\r');
let boundary = temp_bound.as_bytes();
Self::get_multipart_data(data, boundary, &end_boundary, &mut keymap);
}
_ => {
return Err(ParseFormError {
error: ParseErrors::BadData,
})
}
};
Ok(keymap)
}
fn get_multipart_data(
data: &[u8],
boundary: &[u8],
end_boundary: &[u8],
map: &mut HashMap<String, Result<Vec<u8>, ParseFormError>>,
) {
let mut current_part: Vec<&[u8]> = vec![];
let mut current_key: Option<String> = None;
let mut ignore_line = 0;
for part in data.split(|byte| byte == &b'\n') {
if part == &[b'\r'] {
if let Some(_) = current_key {
if ignore_line >= TWO_NEWLINES {
current_part.push(&[b'\n']);
continue;
}
ignore_line += 1;
}
continue;
}
if part == end_boundary {
if let Some(key) = &current_key {
let mut part = current_part.join(&b'\n');
if part.ends_with(&[b'\r']) {
part.pop();
}
map.insert(key.to_string(), Ok(part));
}
break;
}
if part == boundary {
if let Some(key) = &current_key {
let mut part = current_part.join(&b'\n');
if part.ends_with(&[b'\r']) {
part.pop();
}
map.insert(key.to_string(), Ok(part));
}
current_part = vec![];
current_key = None;
ignore_line = 0;
continue;
}
if part.starts_with(b"Content-Disposition: form-data; name=") {
let headers = part
.split(|byte| byte == &b';')
.filter(|header| !header.is_empty())
.collect::<Vec<_>>();
if headers.len() < 2 {
continue;
}
let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>();
if name.len() != 2 {
continue;
}
let mkey = String::from_utf8_lossy(name[1])
.as_ref()
.trim_end()
.trim_matches('"')
.to_owned();
if map.contains_key::<str>(&mkey) {
current_key = Some(mkey.to_owned());
}
continue;
} else if let Some(_) = &current_key {
current_part.push(part);
}
}
}
}
#[cfg(test)]
mod test {
use crate::{
handling::routes::Data,
utils::mime::mime_enum::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData},
};
use super::Request;
#[test]
fn try_post_text() {
let req = Request {
uri: "",
headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()],
method: crate::handling::methods::Method::Post,
cookies: None,
mime_type: Some(ApplicationXWwwFormUrlencoded),
};
let data = Data {
buffer: b"message=23&message1=24".to_vec(),
is_complete: true,
};
let map = req.get_post_data(&["message", "message1"], &data).unwrap();
assert_eq!(
&b"23".to_vec(),
map.get("message").unwrap().as_ref().unwrap()
);
assert_eq!(
&b"24".to_vec(),
map.get("message1").unwrap().as_ref().unwrap()
);
let req = Request {
uri: "",
headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()],
method: crate::handling::methods::Method::Post,
cookies: None,
mime_type: Some(MultipartFormData),
};
let data = Data {
buffer: b"--boundary\r
Content-Disposition: form-data; name=\"field1\"\r
\r
value1\r
--boundary\r
Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r
\r
va\nlue2\r
--boundary--\r
"
.to_vec(),
is_complete: true,
};
let map = req.get_post_data(&["field1", "field2"], &data).unwrap();
assert_eq!(
&b"value1".to_vec(),
map.get("field1").unwrap().as_ref().unwrap()
);
assert_eq!(
&b"va\nlue2".to_vec(),
map.get("field2").unwrap().as_ref().unwrap()
);
}
}
mod cookies;
mod datatypes;
mod form_utils;
mod request_impl;
pub use cookies::extract_cookies_from_vec;
mod request_mime;
pub use datatypes::{MediaType, ParseFormError, Request};
// use std::net::SocketAddr;
use crate::handling::methods::Method;
use std::collections::HashMap;
use crate::{
handling::{methods::Method, routes::Data},
utils::mime::mime_enum::Mime,
};
use super::datatypes::{ParseErrors, ParseFormError, Request};
static TWO_NEWLINES: u8 = 3;
use super::Request;
impl Request<'_> {
pub fn can_have_body(&self) -> bool {
......@@ -24,273 +15,4 @@ impl Request<'_> {
_ => false,
}
}
pub fn get_get_form_keys<'a>(
&'a self,
keys: &'a [&str],
) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> {
let data = if let Some(val) = self.uri.split_once('?') {
val
} else {
return Err(ParseFormError {
error: ParseErrors::NoData,
});
};
let data = data
.1
.split("&")
.map(|kvp| kvp.split_once("="))
.collect::<Vec<Option<(&str, &str)>>>();
let mut values: HashMap<&str, &str> = HashMap::new();
for kvp in data {
let kvp = if let Some(kvp) = kvp {
kvp
} else {
continue;
};
values.insert(kvp.0, kvp.1);
}
let mut response = HashMap::new();
for key in keys {
let entry = if let Some(val) = values.get(key) {
Ok(*val)
} else {
Err(ParseFormError {
error: ParseErrors::NoData,
})
};
response.insert((*key).into(), entry);
}
Ok(response)
}
pub fn get_post_data<'a>(
&'a self,
keys: &[&'a str],
data: &Data,
) -> Result<HashMap<String, Result<Vec<u8>, ParseFormError>>, ParseFormError> {
let boundary;
let post_type = if let Some(val) = self
.headers
.iter()
.find(|header| header.contains("Content-Type: "))
{
let content_type = val.trim().strip_prefix("Content-Type: ").unwrap();
let type_vec = content_type.split(';').collect::<Vec<&str>>();
boundary = if let Some(bound) = type_vec.iter().find(|part| part.contains(" boundary="))
{
bound
.strip_prefix(" boundary=")
.unwrap()
.trim_end()
.trim_matches('"')
} else {
""
};
if let Ok(mime) = type_vec[0].trim().parse() {
mime
} else {
return Err(ParseFormError {
error: ParseErrors::BadData,
});
}
} else {
return Err(ParseFormError {
error: ParseErrors::NoData,
});
};
let data = data.buffer.as_slice();
let mut keymap: HashMap<String, Result<Vec<u8>, ParseFormError>> =
HashMap::with_capacity(keys.len());
for key in keys {
keymap.entry(key.to_string()).or_insert(Err(ParseFormError {
error: ParseErrors::NoData,
}));
}
match post_type {
Mime::ApplicationXWwwFormUrlencoded => {
for kvp in data.split(|byte| *byte == b'&') {
let kvp = kvp
.split(|byte| *byte == b'=')
.map(|list| list.to_vec())
.collect::<Vec<Vec<u8>>>();
let key = if let Some(kv) = kvp.get(0) {
kv
} else {
return Err(ParseFormError {
error: ParseErrors::BadData,
});
};
let key = if let Ok(kv) = String::from_utf8(key.to_vec()) {
kv
} else {
return Err(ParseFormError {
error: ParseErrors::BadData,
});
};
let value = kvp.get(1).ok_or(ParseFormError {
error: ParseErrors::NoData,
});
let thing = if let Some(val) = keymap.get_mut(key.as_str()) {
val
} else {
continue;
};
*thing = match value {
Ok(val) => Ok(val.to_vec()),
Err(err) => Err(err),
}
}
}
Mime::MultipartFormData => {
let mut temp_bound = "--".to_string();
temp_bound.push_str(&format!("{boundary}"));
let end_boundary = format!("{temp_bound}--\r").as_bytes().to_owned();
temp_bound.push('\r');
let boundary = temp_bound.as_bytes();
Self::get_multipart_data(data, boundary, &end_boundary, &mut keymap);
}
_ => {
return Err(ParseFormError {
error: ParseErrors::BadData,
})
}
};
Ok(keymap)
}
fn get_multipart_data(
data: &[u8],
boundary: &[u8],
end_boundary: &[u8],
map: &mut HashMap<String, Result<Vec<u8>, ParseFormError>>,
) {
let parts = data.split(|byte| byte == &b'\n').collect::<Vec<&[u8]>>();
let mut current_part: Vec<&[u8]> = vec![];
let mut current_key: Option<String> = None;
let mut ignore_line = 0;
for part in parts {
if part == &[b'\r'] {
if let Some(_) = current_key {
if ignore_line >= TWO_NEWLINES {
current_part.push(&[b'\n']);
continue;
}
ignore_line += 1;
}
continue;
}
if part == end_boundary {
if let Some(key) = &current_key {
let mut part = current_part.join(&b'\n');
if part.ends_with(&[b'\r']) {
part.pop();
}
map.insert(key.to_string(), Ok(part));
}
break;
}
if part == boundary {
if let Some(key) = &current_key {
let mut part = current_part.join(&b'\n');
if part.ends_with(&[b'\r']) {
part.pop();
}
map.insert(key.to_string(), Ok(part));
}
current_part = vec![];
current_key = None;
ignore_line = 0;
continue;
}
if part.starts_with(b"Content-Disposition: form-data; name=") {
let headers = part
.split(|byte| byte == &b';')
.filter(|header| !header.is_empty())
.collect::<Vec<_>>();
if headers.len() < 2 {
continue;
}
let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>();
if name.len() != 2 {
continue;
}
let mkey = String::from_utf8_lossy(name[1])
.as_ref()
.trim_end()
.trim_matches('"')
.to_owned();
if map.contains_key::<str>(&mkey) {
current_key = Some(mkey.to_owned());
}
continue;
} else if let Some(_) = &current_key {
current_part.push(part);
}
}
}
}
#[cfg(test)]
mod test {
use crate::handling::routes::Data;
use super::Request;
#[test]
fn try_post_text() {
let req = Request {
uri: "",
headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()],
method: crate::handling::methods::Method::Post,
cookies: None,
};
let data = Data {
buffer: b"message=23&message1=24".to_vec(),
is_complete: true,
};
let map = req.get_post_data(&["message", "message1"], &data).unwrap();
assert_eq!(
&b"23".to_vec(),
map.get("message").unwrap().as_ref().unwrap()
);
assert_eq!(
&b"24".to_vec(),
map.get("message1").unwrap().as_ref().unwrap()
);
let req = Request {
uri: "",
headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()],
method: crate::handling::methods::Method::Post,
cookies: None,
};
let data = Data {
buffer: b"--boundary\r
Content-Disposition: form-data; name=\"field1\"\r
\r
value1\r
--boundary\r
Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r
\r
va\nlue2\r
--boundary--\r
"
.to_vec(),
is_complete: true,
};
let map = req.get_post_data(&["field1", "field2"], &data).unwrap();
assert_eq!(
&b"value1".to_vec(),
map.get("field1").unwrap().as_ref().unwrap()
);
assert_eq!(
&b"va\nlue2".to_vec(),
map.get("field2").unwrap().as_ref().unwrap()
);
}
}
use crate::utils::mime::mime_enum::Mime;
use super::datatypes::Request;
impl Request<'_> {
pub fn extract_mime_from_vec(headers: &Vec<String>) -> Option<Mime> {
let Some(content_type_header) = headers
.iter()
.find(|header| header.starts_with("Content-Type: ")) else {
return None;
};
let content_type_string = content_type_header
.strip_prefix("Content-Type: ")
.unwrap()
.to_string();
let mime;
match content_type_string.split_once(';') {
Some(sub) => {
mime = if let Ok(a) = sub.0.trim().parse() {
a
} else {
return None;
};
}
None => {
mime = if let Ok(a) = content_type_string.trim().parse() {
a
} else {
return None;
};
}
}
Some(mime)
}
}
#[cfg(test)]
mod test {
use crate::{handling::request::Request, utils::mime::mime_enum::Mime};
#[test]
pub fn test_mime_parse_from_header_vec() {
let mut right = vec![
"GET / 23".to_string(),
"SDF:LKJSD:F".to_string(),
"Content-Type: text/plain".to_string(),
"SDF".to_string(),
];
let mut wrong = vec!["SDF:LKJSD:F".to_string(), "SDF".to_string()];
assert_eq!(None, Request::extract_mime_from_vec(&mut wrong));
assert_eq!(
Mime::TextPlain,
Request::extract_mime_from_vec(&mut right).unwrap()
);
assert_eq!(None, Request::extract_mime_from_vec(&mut right));
}
}
......@@ -3,7 +3,7 @@ use std::thread::available_parallelism;
use tokio::{net::TcpListener, signal::unix::{SignalKind, signal}, select};
use crate::{
handlers::handlers::handle_connection,
handlers::handler::handle_connection,
handling::routes::{Route, Uri},
};
......@@ -75,8 +75,6 @@ impl<'a> Config {
/// # Panics
/// Panics if there are no Mountpoints in the Confiuration to lauch, as well as when the there
/// is no interrupt signal
///
/// Panics if .
pub async fn launch(self) {
println!("Server launched from http://{}", self.address.local_addr().unwrap());
let mut sigint = signal(SignalKind::interrupt()).unwrap();
......
......@@ -62,6 +62,7 @@ fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Da
if data.is_empty() {
return Outcome::Forward(data);
}
println!("{:?}", request.get_post_data(&["message"], &data));
let dat = if let Ok(val) = request.get_post_data(&["message"], &data) {
post_hi(String::from_utf8_lossy(val.get("message").unwrap().as_ref().unwrap()).to_string())
} else {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment