//! response handles the incoming request parsed `HTTPRequest` //! it will check if the `HTTPRequest` is valid and build an HTTPResponse corresponding to the HTTP //! message specs. see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages //! NOTE: only few parts of the specification has been implemented use crate::http::request::{HTTPRequest, HTTPVersion}; use async_trait::async_trait; use json; // add the Store trait to be used by `FileStore` use crate::stores::FileStore; use crate::stores::Store; #[derive(Debug, PartialEq)] pub enum HTTPStatusCode { Http200, Http400, Http403, Http404, Http500, } impl Into for HTTPStatusCode { fn into(self) -> String { match self { Self::Http200 => "200".to_string(), Self::Http400 => "400".to_string(), Self::Http404 => "404".to_string(), Self::Http403 => "403".to_string(), Self::Http500 => "500".to_string(), } } } pub struct HTTPStatusLine { version: HTTPVersion, status_code: HTTPStatusCode, } impl Default for HTTPStatusLine { fn default() -> HTTPStatusLine { HTTPStatusLine { version: HTTPVersion::Http1_1, status_code: HTTPStatusCode::Http400, } } } impl Into for HTTPStatusLine { fn into(self) -> String { let version: String = self.version.into(); let status_code: String = self.status_code.into(); format! {"{} {}", version, status_code} } } impl HTTPStatusLine { fn set_status_code(&mut self, code: HTTPStatusCode) { self.status_code = code; } pub fn get_status_code(&self) -> &HTTPStatusCode { &self.status_code } } /// represents an HTTP response (headers are not parsed) /// NOTE: for simplicity, only JSON body are accepted pub struct HTTPResponse { pub status_line: HTTPStatusLine, body: json::JsonValue, } impl Default for HTTPResponse { fn default() -> Self { HTTPResponse { status_line: HTTPStatusLine::default(), body: json::parse(r#"{"error": "the incoming request is not valid"}"#).unwrap(), } } } impl Into for HTTPResponse { fn into(self) -> String { // move `self.body` into a new var let b = self.body; let body: String = json::stringify(b); let status_line: String = self.status_line.into(); format!( "{}\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", status_line, body.len(), body ) } } impl HTTPResponse { /// creates a response from the incoming `Request` /// `From` could be used instead of forcing it like this /// it fails using `async_trait` attributes (only custom traits work ?) pub async fn from(request: HTTPRequest) -> Self { let mut response = HTTPResponse::default(); if !request.is_valid() { return response; } // empty body -> invalid request (credentials needed) if let None = request.body { return Self::as_403(); } // TODO: path to `store.txt` must not be hardcoded, should be in a config file and load at // runtime let mut store = FileStore::new("tests/data/store.txt".to_string()); let body = request.body.unwrap(); let is_auth = store.is_auth(&body.get_data()).await; if !is_auth { return Self::as_403(); } // TODO: must be a valid JWT (to implement) let body = json::parse( r#"{"token": "header.payload.signature", "refresh": "header.payload.signature"}"#, ) .unwrap(); response.status_line.version = request.start_line.version; response.status_line.status_code = HTTPStatusCode::Http200; response.body = body; response } /// generates a 403 response with a correct error message pub fn as_403() -> Self { let mut response = HTTPResponse { status_line: HTTPStatusLine::default(), body: json::parse(r#"{"error": "invalid credentials"}"#).unwrap(), }; response .status_line .set_status_code(HTTPStatusCode::Http403); response } }