immortal_http/
response.rs

1
2use std::rc::Rc;
3use std::collections::HashMap;
4use std::cell::RefCell;
5use std::sync::Arc;
6
7use crate::session::SessionManager;
8use crate::request::Request;
9use crate::cookie::Cookie;
10
11use debug_print::debug_eprintln;
12use lazy_static::lazy_static;
13use chrono::{DateTime, Utc};
14use uuid::Uuid;
15
16lazy_static! {
17    static ref STATUSES: HashMap<String, String> = HashMap::from([
18            ( "200".to_string(), "OK".to_string() ),
19            ( "301".to_string(), "MOVED PERMANENTLY".to_string() ),
20            ( "302".to_string(), "FOUND".to_string() ),
21            ( "308".to_string(), "PERMANENT REDIRECT".to_string() ),
22            ( "400".to_string(), "BAD REQUEST".to_string() ),
23            ( "401".to_string(), "UNAUTHORIZED".to_string() ),
24            ( "403".to_string(), "FORBIDDEN".to_string() ),
25            ( "404".to_string(), "NOT FOUND".to_string() ),
26            ( "411".to_string(), "LENGTH REQUIRED".to_string() ),
27            ( "413".to_string(), "PAYLOAD TOO LARGE".to_string() ),
28            ( "414".to_string(), "URI TOO LONG".to_string() ),
29            ( "418".to_string(), "I AM A TEAPOT".to_string() ),
30            ( "426".to_string(), "UPGRADE REQUIRED".to_string() ),
31            ( "451".to_string(), "UNAVAILABLE FOR LEGAL REASONS".to_string() ),
32            ( "500".to_string(), "INTERNAL SERVER ERROR".to_string() ),
33            ( "501".to_string(), "NOT IMPLEMENTED".to_string() ),
34            ( "505".to_string(), "HTTP VERSION NOT SUPPORTED".to_string() ),
35        ]);
36}
37
38#[derive(Debug)]
39pub struct Response<'req> {
40    pub body: Vec<u8>,
41    pub code: &'req str,
42    pub status: &'req str,
43    pub protocol: &'req str,
44    pub method: &'req str,
45    pub headers: HashMap<&'req str, String>,
46    pub cookies: Vec<Cookie<'req>>,
47}
48
49impl<'req> Response<'req> {
50    /// Constructs a default response based on the passed request.
51    pub fn new(
52        req: Rc<RefCell<Request<'req>>>,
53        session_manager: Arc<SessionManager>,
54        session_id: &mut Uuid
55    ) -> Self {
56        let mut headers: HashMap<&str, String> = HashMap::new();
57
58        // default headers
59        headers.insert("Connection", "close".to_string());
60        headers.insert("Content-Type", "text/html".to_string());
61
62        let sm_is_enabled = session_manager.is_enabled();
63        if sm_is_enabled {
64            if let Some(cookie) = req.borrow_mut().cookie("id") {
65                if let Ok(id) = cookie.value.parse::<Uuid>() {
66                    *session_id = id;
67                }
68            }
69        }
70
71        let sm_should_gen_id = session_manager.is_enabled() 
72                && !session_manager.add_session(*session_id) 
73                && !session_manager.session_exists(*session_id);
74
75        let mut should_add_cookie = false;
76        if sm_should_gen_id {
77            *session_id = SessionManager::generate_id();
78            should_add_cookie = true;
79        }
80
81        let mut cookies: Vec<Cookie> = Vec::new();
82        let sm_is_enabled = session_manager.is_enabled();
83        if sm_is_enabled && should_add_cookie && !session_id.is_nil() {
84            let cookie = Cookie::builder()
85                .name("id")
86                .value(session_id.to_string().as_str())
87                .http_only(true)
88                .build();
89            cookies.push(cookie);
90        }
91
92        Self {
93            body: vec![],
94            code: "200",
95            status: "OK",
96            protocol: "HTTP/1.1",
97            method: req.borrow_mut().method,
98            headers,
99            cookies,
100        }
101    }
102
103    /// Constructs a default error response
104    pub fn bad() -> Self {
105        let mut headers: HashMap<&str, String> = HashMap::new();
106
107        // default headers
108        headers.insert("Connection",  "close".to_string());
109        headers.insert("Content-Type", "text/html".to_string());
110
111        Self {
112            body: vec![],
113            code: "400",
114            status: "BAD REQUEST",
115            protocol: "HTTP/1.1",
116            method: "GET",
117            headers,
118            cookies: Vec::new(),
119        }
120    }
121
122    /// Generates the serial data for an HTTP response using the object internal state
123    pub fn serialize(&mut self) -> Vec<u8> {
124        let mut serialized = vec![];
125
126        let mut status = match STATUSES.get(self.code) {
127            None => self.status,
128            Some(thing) => thing,
129        };
130
131        if status.is_empty() {
132            debug_eprintln!("ERROR: No default status string for HTTP {}, sending 500", self.code);
133            self.code = "500";
134            status = match STATUSES.get(self.code) {
135                None => "INTERNAL SERVER ERROR",
136                Some(thing) => thing,
137            };
138            self.headers.insert("Content-Type", "text/html".to_string());
139            self.body = format!("<h1>500: {}</h1>", status).into_bytes();
140        }
141
142        if !self.cookies.is_empty() {
143            self.headers.insert("Set-Cookie", self.cookies.iter()
144                                .map(|c| c.to_string())
145                                .intersperse("; ".to_string())
146                                .reduce(|acc, c| acc + &c).unwrap());
147        }
148
149        // emit the status line
150        serialized.append(&mut format!("{} {} {}\r\n", &self.protocol, &self.code, &status).into_bytes());
151
152        let now: DateTime<Utc> = Utc::now();
153        self.headers.insert("Date", now.format("%a, %d %b %Y %H:%M:%S").to_string());
154
155        // emit headers
156        for (key, value) in self.headers.iter() {
157            if !key.is_empty() {
158                serialized.append(&mut format!("{}: {}\r\n", &key, &value).into_bytes());
159            }
160        }
161
162        // output content or not depending on the request method
163        if self.method != "HEAD" {
164            serialized.append(&mut format!("Content-Length: {}\r\n\r\n", &self.body.len()).into_bytes());
165            serialized.append(&mut self.body);
166        } else {
167            serialized.append(&mut "Content-Length: 0\r\n\r\n".to_string().into_bytes());
168        }
169
170        serialized
171    }
172
173    /// looks up headers and returns it
174    pub fn header(&self, key: &str) -> Option<&str> {
175        match self.headers.get(key) {
176            None => None,
177            Some(thing) => Some(thing.as_str()),
178        }
179    }
180
181    pub fn is_redirect(&self) -> bool {
182        let mut cases = 0;
183        if self.code.starts_with('3') {
184            cases += 1;
185        }
186        if self.header("Location").is_some() {
187            cases += 1;
188        }
189        cases == 2
190    }
191}
192