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

Add multipart post text form support, check for `Content-Length` Header

parent 2306b7b2
No related branches found
No related tags found
No related merge requests found
...@@ -12,7 +12,7 @@ use crate::setup::MountPoint; ...@@ -12,7 +12,7 @@ use crate::setup::MountPoint;
pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) { pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) {
let mut buf_reader = BufReader::new(&mut stream); let mut buf_reader = BufReader::new(&mut stream);
let mut http_request: Vec<String> = Vec::with_capacity(100); let mut http_request: Vec<String> = Vec::with_capacity(30);
loop { loop {
let mut buffer = String::new(); let mut buffer = String::new();
if let Err(_) = buf_reader.read_line(&mut buffer).await { if let Err(_) = buf_reader.read_line(&mut buffer).await {
...@@ -74,26 +74,28 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin ...@@ -74,26 +74,28 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin
if let Ok(size) = len { if let Ok(size) = len {
size size
} else { } else {
0 eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method);
len_not_defined(stream, Status::LengthRequired).await;
return;
} }
} else { } 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;
return;
}
0 0
}; };
if length != 0 {
let mut buffer: Vec<u8> = vec![]; let mut buffer: Vec<u8> = vec![];
buf_reader.read_buf(&mut buffer).await.unwrap(); buf_reader.read_buf(&mut buffer).await.unwrap();
if buffer.len() != length { if buffer.len() != length {
let respone = len_not_defined(Status::LengthRequired); len_not_defined(stream, Status::LengthRequired).await;
let mut response = respone.0.as_bytes().to_vec();
response.extend_from_slice(&respone.1);
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");
return; return;
} }
return; data.is_complete = true;
data.buffer = buffer;
} }
data.is_complete = true;
data.buffer = buffer;
} }
let mut handled_response: Option<Outcome<Response, Status, Data>> = None; let mut handled_response: Option<Outcome<Response, Status, Data>> = None;
...@@ -165,14 +167,11 @@ fn failure_handler(status: Status) -> (String, Vec<u8>) { ...@@ -165,14 +167,11 @@ fn failure_handler(status: Status) -> (String, Vec<u8>) {
) )
} }
fn len_not_defined(status: Status) -> (String, Vec<u8>) { async fn len_not_defined(mut stream: TcpStream, status: Status) {
let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); 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();
format!( response.extend_from_slice(&page_411.get_data());
"HTTP/1.1 {status}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", if let Err(e) = stream.write_all(&response).await {
page_411.get_len(), eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
page_411.get_mime() }
),
page_411.get_data(),
)
} }
...@@ -2,10 +2,7 @@ ...@@ -2,10 +2,7 @@
use std::{collections::HashMap, error::Error, fmt::Display}; use std::{collections::HashMap, error::Error, fmt::Display};
trait FromPost {} use crate::utils::mime::mime_enum::Mime;
impl FromPost for &str {}
impl FromPost for Vec<u8> {}
use super::{ use super::{
methods::Method, methods::Method,
...@@ -68,6 +65,12 @@ impl Request<'_> { ...@@ -68,6 +65,12 @@ impl Request<'_> {
_ => false, _ => false,
} }
} }
pub fn mandatory_body(&self) -> bool {
match self.method {
Method::Post | Method::Put | Method::Patch => true,
_ => false,
}
}
// pub fn get_post_form_key<T: FromRequest>(&self, data: Data) -> T {} // pub fn get_post_form_key<T: FromRequest>(&self, data: Data) -> T {}
pub fn get_get_form_keys( pub fn get_get_form_keys(
&self, &self,
...@@ -108,7 +111,132 @@ impl Request<'_> { ...@@ -108,7 +111,132 @@ impl Request<'_> {
} }
Ok(response) Ok(response)
} }
pub fn get_post_form_key(&self, key: &str, data: Data) { pub fn get_post_text_form_key(&self, key: &str, data: &Data) -> Result<String, ()> {
todo!() let mut post_type = self
.headers
.iter()
.find(|header| header.contains("Content-Type: "))
.unwrap()
.to_string();
post_type = post_type
.strip_prefix("Content-Type: ")
.unwrap()
.to_string();
let post_type: Vec<&str> = post_type.trim().split(';').collect();
let mime_type = post_type[0].parse::<Mime>().unwrap();
match mime_type {
Mime::ApplicationXWwwFormUrlencoded => {
let data = String::from_utf8(data.buffer.clone()).unwrap();
let kvps = data
.split("&")
.map(|kvp| kvp.split_once("=").unwrap())
.collect::<HashMap<&str, &str>>();
if let Some(val) = kvps.get(key) {
Ok(val.to_string())
} else {
Err(())
}
}
Mime::MultipartFormData => {
let from_req = post_type[1..]
.iter()
.find(|val| val.contains("boundary="))
.unwrap()
.strip_prefix("boundary=")
.unwrap();
let mut boundary = "--".as_bytes().to_vec();
boundary.extend_from_slice(from_req.trim_matches('"').as_bytes());
let mut end_boundary = boundary.clone();
end_boundary.extend_from_slice(b"--");
boundary.extend_from_slice(&[b'\r']);
let parts = data
.buffer
.split(|byte| byte == &b'\n')
.collect::<Vec<&[u8]>>();
let mut boundary_found = false;
let mut key_found = false;
let mut value = vec![];
for part in parts {
if part == [] {
continue;
}
if (key_found && part == boundary) || part == end_boundary {
break;
}
if !boundary_found && part == boundary {
boundary_found = true;
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])
.to_string()
.trim_end()
.trim_matches('"')
.to_owned();
key_found = key == mkey;
} else if key_found == true {
value.extend_from_slice(part);
value.extend_from_slice(&[b'\n']);
}
}
Ok(String::from_utf8_lossy(&value)
.to_owned()
.trim()
.to_string())
}
_ => Err(()),
}
}
}
#[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: multipart/form-data;boundary=\"boundary\"".to_string()],
method: crate::handling::methods::Method::Post,
};
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\"\n\r
\r
value2\r
--boundary--"
.to_vec(),
is_complete: true,
};
assert_eq!(
"value1",
req.get_post_text_form_key("field1", &data).unwrap()
);
assert_eq!(
"value2",
req.get_post_text_form_key("field2", &data).unwrap()
);
} }
} }
...@@ -21,10 +21,10 @@ pub trait ResponseBody: Send { ...@@ -21,10 +21,10 @@ pub trait ResponseBody: Send {
impl ResponseBody for Body { impl ResponseBody for Body {
fn get_data(&self) -> Vec<u8> { fn get_data(&self) -> Vec<u8> {
self.body.clone() self.body()
} }
fn get_mime(&self) -> Mime { fn get_mime(&self) -> Mime {
Mime::TextPlain self.mime_type()
} }
fn get_len(&self) -> usize { fn get_len(&self) -> usize {
......
use super::{ use crate::{
methods::Method, handling::{
request::{MediaType, Request}, methods::Method,
response::{Outcome, Response, Status}, request::{MediaType, Request},
response::{Outcome, Response, Status},
},
utils::mime::mime_enum::Mime,
}; };
pub struct RoutInfo { pub struct RoutInfo {
...@@ -63,7 +66,26 @@ pub type Uri<'a> = &'a str; ...@@ -63,7 +66,26 @@ pub type Uri<'a> = &'a str;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Body { pub struct Body {
pub body: Vec<u8>, body: Vec<u8>,
mime_type: Mime,
}
impl Body {
pub fn new(body: Vec<u8>, mime_type: Mime) -> Self {
Self { body, mime_type }
}
pub fn set_mime_type(&mut self, mime_type: Mime) {
self.mime_type = mime_type;
}
pub fn set_body(&mut self, body: Vec<u8>) {
self.body = body;
}
pub fn mime_type(&self) -> Mime {
self.mime_type
}
pub fn body(&self) -> Vec<u8> {
self.body.clone()
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
......
...@@ -66,6 +66,7 @@ impl<'a> Config { ...@@ -66,6 +66,7 @@ impl<'a> Config {
self self
} }
pub async fn launch(self) { pub async fn launch(self) {
println!("Server launched from http://{}", self.address.local_addr().unwrap());
let mut sigint = signal(SignalKind::interrupt()).unwrap(); let mut sigint = signal(SignalKind::interrupt()).unwrap();
loop { loop {
select! { select! {
......
...@@ -4061,6 +4061,11 @@ impl std::str::FromStr for Mime { ...@@ -4061,6 +4061,11 @@ impl std::str::FromStr for Mime {
type Err = ParseMimeError; type Err = ParseMimeError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = if let Some(str) = s.split_once(';') {
str.0
} else {
s
};
match MIME_MAP.get(s).copied() { match MIME_MAP.get(s).copied() {
Some(mimetype) => Ok(mimetype), Some(mimetype) => Ok(mimetype),
None => Err(ParseMimeError(1)), None => Err(ParseMimeError(1)),
......
...@@ -7,13 +7,10 @@ ...@@ -7,13 +7,10 @@
<body> <body>
<h1>404</h1> <h1>404</h1>
<p>Hi from Rust</p> <p>Hi from Rust</p>
<form action="/" method="POST"> <form action="/post/post" method="post">
<label for="message">Enter your message:</label> <label for="message">Enter your message:</label>
<input type="text" id="message" name="message" /> <input type="text" id="message" name="message" />
<button type="submit">Send</button> <button type="submit">Send</button>
<label for="message">Enter your message:</label>
<input type="text" id="asdf" name="message" />
<button type="submit">Send</button>
</form> </form>
<form action="/static/hi" method="get"> <form action="/static/hi" method="get">
<label for="jump">Enter asdf</label> <label for="jump">Enter asdf</label>
......
...@@ -58,12 +58,12 @@ fn fileserver(path: &str) -> Result<NamedFile, Status> { ...@@ -58,12 +58,12 @@ fn fileserver(path: &str) -> Result<NamedFile, Status> {
NamedFile::open(PathBuf::from("static/".to_string() + path)) NamedFile::open(PathBuf::from("static/".to_string() + path))
} }
fn post_hi_handler(_request: Request, data: Data) -> Outcome<Response, Status, Data> { fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Data> {
if data.is_empty() { if data.is_empty() {
return Outcome::Forward(data); return Outcome::Forward(data);
} }
let data = if let Ok(str) = String::from_utf8(data.buffer) { let data = if let Ok(val) = request.get_post_text_form_key("message", &data) {
str val
} else { } else {
return Outcome::Failure(Status::BadRequest); return Outcome::Failure(Status::BadRequest);
}; };
...@@ -111,7 +111,7 @@ async fn main() { ...@@ -111,7 +111,7 @@ async fn main() {
rank: 0, rank: 0,
}; };
http::build("192.168.178.32:8000") http::build("127.0.0.1:8000")
.await .await
.mount("/", vec![fileserver, post_test, static_hi]) .mount("/", vec![fileserver, post_test, static_hi])
.mount("/post/", vec![post_test]) .mount("/post/", vec![post_test])
......
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