blob: cf68f22aa9bb3cfae0557c229624c02ffa446278 [file] [log] [blame]
use std::{io, collections::HashMap, env::args, os::unix::fs::MetadataExt};
use bytes::BytesMut;
use tokio::{net::{TcpListener, TcpStream}, io::{AsyncReadExt, AsyncWriteExt}, fs::File};
use itertools::Itertools;
use nom::AsChar;
#[derive(Debug)]
enum BodyTypes {
File(File),
String(String)
}
#[derive(PartialEq)]
enum RequestSection {
RequestLine,
Headers,
Body
}
async fn process_socket(mut stream: TcpStream) -> Result<(), &'static str> {
println!("accepted new connection");
let mut request: HashMap<String, String> = HashMap::new();
let mut section: RequestSection = RequestSection::RequestLine;
loop {
let mut line = String::default();
if section == RequestSection::Body {
let content_size: usize = request.get("Content-Length").unwrap().parse::<usize>().unwrap();
let mut buf: Vec<u8> = vec![0; content_size];
stream.read_exact(&mut buf).await.unwrap();
for c in buf {
line.push(c.as_char());
}
} else {
loop {
let mut buf: [u8; 1] = [0; 1];
stream.read_exact(&mut buf).await.unwrap();
let character = buf[0].as_char();
if character == '\n' {
break
} else if character != '\r' {
line.push(character);
}
}
}
match section {
RequestSection::RequestLine => {
let mut splits = line.split(' ');
request.insert("method".to_owned(), splits.next().unwrap().to_owned());
request.insert("path".to_owned(), splits.next().unwrap().to_owned());
section = RequestSection::Headers;
}
RequestSection::Headers => {
if line == "" {
section = RequestSection::Body;
} else {
let mut splits = line.split(":");
let key = splits.next().unwrap();
let value = splits.next().unwrap_or_default().trim();
request.insert(key.to_owned(), value.to_owned());
}
},
RequestSection::Body => {
request.insert("body".to_owned(), line);
break;
},
}
if request.get("path").unwrap() == "/user-agent" && !request.contains_key("User-Agent") { continue; };
if request.get("path").unwrap().starts_with("/files/") && request.get("method").unwrap() == "POST" && !request.contains_key("body") { continue; };
break;
}
let mut path = request.get("path").unwrap().split("/");
let response_status: String;
let mut response_headers: Vec<String> = vec![];
let response_body: BodyTypes;
path.next().unwrap();
match path.next().unwrap() {
"" => {
response_status = "200 OK".into();
response_body = BodyTypes::String("".into());
},
"echo" => {
response_status = "200 OK".into();
response_body = BodyTypes::String(path.join("/"));
response_headers.push("Content-Type: text/plain".into());
},
"user-agent" => {
response_status = "200 OK".into();
response_body = BodyTypes::String(request.get("User-Agent").unwrap().into());
response_headers.push("Content-Type: text/plain".into());
},
"files" => {
let args: Vec<String> = args().collect();
let dir_arg = args.iter().find_position(|item| **item == "--directory");
let file_name = path.join("/");
let file_path = if dir_arg.is_some() {
args[dir_arg.unwrap().0+1].to_owned() + "/" + file_name.as_str()
} else {
return Err("No directory Given".into())
};
match request.get("method").unwrap().as_str() {
"GET" => {
let maybe_file = File::open(file_path).await;
match maybe_file {
Ok(file) => {
response_status = "200 OK".into();
response_body = BodyTypes::File(file);
response_headers.push("Content-Type: application/octet-stream".into());
},
Err(_) => {
response_status = "404 Not Found".into();
response_body = BodyTypes::String("".into());
}
}
},
"POST" => {
let file = File::create(file_path).await;
match file {
Ok(mut f) => {
f.write(request.get("body").unwrap().as_bytes()).await.unwrap();
response_status = "201 Created".into();
response_body = BodyTypes::String("".into());
},
Err(_) => {
response_body = BodyTypes::String("500 INTERNAL SERVER ERROR".into());
response_status = "".into()
}
}
},
_ => unreachable!() // not actually unreachable, but please don't kill our server thank you with pretty puppies on top <3
}
},
_ => {
response_status = "404 Not Found".into();
response_body = BodyTypes::String("".into());
}
}
match response_body {
BodyTypes::String(ref content) => {
response_headers.push(format!("Content-Length: {}", content.len()));
},
BodyTypes::File(ref file) => {
response_headers.push(format!("Content-Length: {}", file.metadata().await.unwrap().size()));
}
}
stream.write(format!("HTTP/1.1 {response_status}\r\n{}\r\n\r\n", response_headers.join("\r\n")).as_bytes()).await.unwrap();
match response_body {
BodyTypes::String(ref content) => {
stream.write(content.as_bytes()).await.unwrap();
},
BodyTypes::File(mut file) => {
loop {
let mut buf = BytesMut::with_capacity(1024);
let read = file.read_buf(&mut buf).await.unwrap();
if read == 0 {
break;
}
stream.write(&mut buf).await.unwrap();
}
}
}
Ok(())
}
#[tokio::main]
async fn main() -> io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:4221").await?;
let mut futures = vec![];
loop {
let (socket, _) = listener.accept().await?;
futures.push(tokio::spawn(process_socket(socket)));
}
}