From 782ad13e4812a570dcc334533c706c187c295962 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Sun, 16 May 2021 12:23:34 +0200 Subject: [PATCH] Http responses contain body --- src/http/consts.rs | 63 +++++++++++++++++++++++++++++ src/http/handler.rs | 36 ++++++++--------- src/http/mod.rs | 82 +++++++++++++++++++++++++++++++++----- src/main.rs | 1 + src/{udp.rs => udp/mod.rs} | 0 src/usimp/mod.rs | 6 ++- src/websocket/mod.rs | 17 ++++++++ 7 files changed, 175 insertions(+), 30 deletions(-) rename src/{udp.rs => udp/mod.rs} (100%) create mode 100644 src/websocket/mod.rs diff --git a/src/http/consts.rs b/src/http/consts.rs index d81469d..5728c1f 100644 --- a/src/http/consts.rs +++ b/src/http/consts.rs @@ -89,3 +89,66 @@ pub static HTTP_STATUSES: [(u16, StatusClass, &str, &str); 41] = [ (505, ServerError, "HTTP Version Not Supported", "The server does not support, or refuses to support, the HTTP protocol version that was used in the request message."), ]; + +pub static DEFAULT_DOCUMENT: &str = "\ + \n\ + \n\ + \n\ + \t{status_code} {status_message} - Locutus - {hostname}\n\ + \t\n\ + \t\n\ + \t\n\ + \t\n\ + \t\n\ + \t\n\ + \t\n\ + \n\ + \n\ + \t
\n\ + \t\t
\n\ + {doc}\ + \t\t\t
{hostname} - {server_str}
\n\ + \t\t
\n\ + \t
\n\ + \n\ + \n"; + +pub static ERROR_DOCUMENT: &str = "\ + \t\t\t

{code}

\n\ + \t\t\t

{message} :(

\n\ + \t\t\t

{desc}

\n\ + \t\t\t

{info}

\n"; + +pub static WARNING_DOCUMENT: &str = "\ + \t\t\t

{code}

\n\ + \t\t\t

{message} :o

\n\ + \t\t\t

{desc}

\n\ + \t\t\t

{info}

\n"; + +pub static SUCCESS_DOCUMENT: &str = "\ + \t\t\t

{code}

\n\ + \t\t\t

{message} :)

\n\ + \t\t\t

{desc}

\n\ + \t\t\t

{info}

\n"; + +pub static INFO_DOCUMENT: &str = "\ + \t\t\t

{code}

\n\ + \t\t\t

{message} :)

\n\ + \t\t\t

{desc}

\n\ + \t\t\t

{info}

\n"; diff --git a/src/http/handler.rs b/src/http/handler.rs index a203a26..3c33647 100644 --- a/src/http/handler.rs +++ b/src/http/handler.rs @@ -1,13 +1,14 @@ use super::Method; use crate::usimp; +use crate::websocket; use chrono; use json; pub struct HttpStream { - stream: super::Stream, - request_num: u32, - client_keep_alive: bool, - server_keep_alive: bool, + pub stream: super::Stream, + pub request_num: u32, + pub client_keep_alive: bool, + pub server_keep_alive: bool, } pub fn connection_handler(client: super::Stream) { @@ -29,20 +30,10 @@ pub fn connection_handler(client: super::Stream) { fn request_handler(client: &mut super::HttpStream) { let mut res = super::Response::new(); - res.add_header("Server", "Locutus"); - res.add_header( - "Date", - chrono::Utc::now() - .format("%a, %d %b %Y %H:%M:%S GMT") - .to_string() - .as_str(), - ); let req = super::parser::parse_request(&mut client.stream).unwrap(); println!("{} {}", req.method, req.uri); - let doc = "Locutus Server

Hello World! :D

"; - if !req.uri.starts_with("/") || req.uri.contains("/./") || req.uri.contains("/../") @@ -53,16 +44,22 @@ fn request_handler(client: &mut super::HttpStream) { res.status(404); } else if req.uri.eq("/") { res.status(200); - res.add_header("Content-Length", doc.len().to_string().as_str()); - res.add_header("Content-Type", "text/html; charset=utf-8"); + } else if req.uri.eq("/_usimp/websocket") { + return websocket::connection_handler(client, &req); } else if req.uri.starts_with("/_usimp/") { let parts: Vec<&str> = req.uri.split('/').collect(); match parts[2..] { ["entity", entity] => res.status(501), - [func] => match usimp::is_valid(func) { + [endpoint] => match usimp::is_valid_endpoint(endpoint) { true => match req.method { - Method::POST => res.status(200), - _ => res.status(405), + Method::POST => { + // TODO + res.status(200) + } + _ => { + res.status(405); + res.add_header("Allow", "POST"); + } }, false => res.status(400), }, @@ -73,6 +70,5 @@ fn request_handler(client: &mut super::HttpStream) { } res.send(&mut client.stream).unwrap(); - client.stream.write_all(doc.as_bytes()).unwrap(); client.server_keep_alive = false; } diff --git a/src/http/mod.rs b/src/http/mod.rs index 04ced60..6fdc7fb 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -95,16 +95,18 @@ impl StatusClass { pub struct Status { code: u16, message: String, + desc: &'static str, class: StatusClass, } impl Status { pub fn from_code(status_code: u16) -> Status { - for (code, class, msg, _desc) in &consts::HTTP_STATUSES { + for (code, class, msg, desc) in &consts::HTTP_STATUSES { if *code == status_code { return Status { code: status_code, message: msg.to_string(), + desc, class: class.clone(), }; } @@ -116,10 +118,12 @@ impl Status { if status_code < 100 || status_code > 599 { panic!("invalid status code"); } + let status = Status::from_code(status_code); Status { code: status_code, message: message.to_string(), - class: StatusClass::from_code(status_code), + desc: status.desc, + class: status.class, } } } @@ -137,8 +141,8 @@ impl std::fmt::Display for HeaderField { pub struct Request { version: String, - method: Method, - uri: String, + pub method: Method, + pub uri: String, header_fields: Vec, } @@ -149,7 +153,7 @@ pub struct Response { } impl Response { - fn new() -> Response { + pub fn new() -> Response { Response { version: "1.1".to_string(), status: Status::from_code(200), @@ -157,18 +161,73 @@ impl Response { } } - fn status(&mut self, status_code: u16) { + pub fn status(&mut self, status_code: u16) { self.status = Status::from_code(status_code) } - fn add_header(&mut self, name: &str, value: &str) { + pub fn add_header(&mut self, name: &str, value: &str) { self.header_fields.push(HeaderField { name: String::from(name), value: String::from(value), }); } - fn send(&self, stream: &mut Stream) -> Result<(), std::io::Error> { + pub fn find_header(&self, header_name: &str) -> Option { + for field in &self.header_fields { + if field + .name + .to_lowercase() + .eq(header_name.to_ascii_lowercase().as_str()) + { + return Some(field.value.clone()); + } + } + return None; + } + + pub fn send(&mut self, stream: &mut Stream) -> Result<(), std::io::Error> { + self.add_header("Server", "Locutus"); + self.add_header( + "Date", + chrono::Utc::now() + .format("%a, %d %b %Y %H:%M:%S GMT") + .to_string() + .as_str(), + ); + + let mut buf = None; + if let None = self.find_header("Content-Length") { + let (doc, color_name, color) = match self.status.class { + StatusClass::Informational => (consts::INFO_DOCUMENT, "info", "#606060"), + StatusClass::Success => (consts::SUCCESS_DOCUMENT, "success", "#008000"), + StatusClass::Redirection => (consts::WARNING_DOCUMENT, "warning", "#E0C000"), + StatusClass::ClientError => (consts::ERROR_DOCUMENT, "error", "#C00000"), + StatusClass::ServerError => (consts::ERROR_DOCUMENT, "error", "#C00000"), + }; + + let new_buf = consts::DEFAULT_DOCUMENT + .replace("{status_code}", self.status.code.to_string().as_str()) + .replace("{status_message}", self.status.message.as_str()) + .replace("{hostname}", "localhost") // TODO hostname + .replace("{theme_color}", color) + .replace("{color_name}", color_name) + .replace("{server_str}", "Locutus server") // TODO server string + .replace( + "{doc}", + doc.replace("{code}", self.status.code.to_string().as_str()) + .replace("{message}", self.status.message.as_str()) + .replace("{desc}", self.status.desc) + .replace("{info}", "") // TODO info string + .as_str(), + ) + .replace("{{", "{") + .replace("}}", "}"); + + self.add_header("Content-Length", new_buf.len().to_string().as_str()); + self.add_header("Content-Type", "text/html; charset=utf-8"); + buf = Some(new_buf); + } + let mut header = format!( "HTTP/{} {:03} {}\r\n", self.version, self.status.code, self.status.message @@ -177,7 +236,12 @@ impl Response { header.push_str(format!("{}: {}\r\n", header_field.name, header_field.value).as_str()); } header.push_str("\r\n"); - stream.write_all(header.as_bytes()) + + stream.write_all(header.as_bytes())?; + if let Some(buf) = buf { + stream.write_all(buf.as_bytes()); + } + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index a50ce5f..89a8470 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod http; mod udp; mod usimp; +mod websocket; use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream}; use std::net::{TcpListener, UdpSocket}; diff --git a/src/udp.rs b/src/udp/mod.rs similarity index 100% rename from src/udp.rs rename to src/udp/mod.rs diff --git a/src/usimp/mod.rs b/src/usimp/mod.rs index 4e16b6f..72dc0e5 100644 --- a/src/usimp/mod.rs +++ b/src/usimp/mod.rs @@ -1,3 +1,7 @@ -pub fn is_valid(evt: &str) -> bool { +use json; + +pub fn is_valid_endpoint(endpoint: &str) -> bool { false } + +pub fn endpoint(endpoint: &str, input: json::object::Object) {} diff --git a/src/websocket/mod.rs b/src/websocket/mod.rs new file mode 100644 index 0000000..7a56c89 --- /dev/null +++ b/src/websocket/mod.rs @@ -0,0 +1,17 @@ +use crate::http; + +pub fn connection_handler(client: &mut http::HttpStream, req: &http::Request) { + client.server_keep_alive = false; + let mut res = http::Response::new(); + + if let http::Method::GET = req.method { + res.status(501); + } else { + res.status(405); + res.add_header("Allow", "GET"); + res.send(&mut client.stream); + return; + } + + res.send(&mut client.stream); +}