http_1_0_parser.rs
1 use std::fs; 2 3 use chrono::prelude::*; 4 5 use crate::content_fetcher::{file_retriever, path_retriever}; 6 7 use crate::http::http_init::{HttpVersion, RequestLine, RequestMethod, StatusLine}; 8 use crate::needed::Polar; 9 10 // reference: https://datatracker.ietf.org/doc/html/rfc1945#section-6 11 #[allow(non_camel_case_types)] 12 pub(crate) struct http_1_0_response { 13 pub(crate) status_line: StatusLine, 14 pub(crate) general_headers: Vec<GeneralHeader>, 15 pub(crate) response_headers: Vec<ResponseHeader>, 16 pub(crate) entity_headers: Vec<EntityHeader>, 17 pub(crate) body: Vec<u8>, 18 } 19 20 21 // reference: https://datatracker.ietf.org/doc/html/rfc1945#section-5 22 #[allow(non_camel_case_types)] 23 struct http_1_0_request { 24 request_line: RequestLine, 25 general_headers: Vec<GeneralHeader>, 26 request_headers: Vec<RequestHeader>, 27 entity_headers: Vec<EntityHeader>, 28 body: Option<Vec<u8>>, 29 } 30 31 pub(crate) enum GeneralHeader { 32 Date(String), // http-date of when the message was sent 33 Pragma(String), // this can specify that it shouldn't get cached material 34 } 35 36 enum RequestHeader { 37 Authorization(String), // authorization for the url 38 From(String), // from what email the request was sent from... why is this a thing? 39 IfModifiedSince(String), // http-date 40 Referer(String), // the uri from where the client was before 41 UserAgent(String), // the user agent of the browser, silly 42 } 43 44 pub(crate) enum ResponseHeader { 45 Location(String), // the new location if a file is moved somewhere 46 Server(String), // the name of the server and any info 47 WWWAuthenticate(String), // tells the client how it should authenticate (for 401) 48 } 49 50 51 // only exists when there is a body :3 52 pub(crate) enum EntityHeader { 53 Allow(Vec<RequestMethod>), // what request methods is supported on the page, should be ignored with POST 54 ContentEncoding(String), // encodings (e.g. gzip) that the client supports 55 ContentLength(u128), // length of "content" (body) in bytes 56 ContentType(String), // the media-type of the content 57 Expires(String), // for how long it should be cached, in http-date 58 LastModified(String), // when the client things it was last modified, in http-date 59 ExtensionHeader(String, String), // custom headers that aren't in the http 1.0 spec 60 } 61 62 63 fn parser(request: Vec<String>, request_line: RequestLine) -> http_1_0_request { 64 let mut general_headers = Vec::new(); 65 let mut request_headers = Vec::new(); 66 let mut entity_headers = Vec::new(); 67 68 for line in request { 69 if let Some((header_name, header_value)) = line.split_once(": ") { 70 let header_value = header_value.to_string(); // overshadow for ease of use lmao 71 72 match header_name { 73 // General Headers 74 "Date" => general_headers.push(GeneralHeader::Date(header_value)), 75 "Pragma" => general_headers.push(GeneralHeader::Pragma(header_value)), 76 77 // Request Headers 78 "Authorization" => request_headers.push(RequestHeader::Authorization(header_value)), 79 "From" => request_headers.push(RequestHeader::From(header_value)), 80 "If-Modified-Since" => request_headers.push(RequestHeader::IfModifiedSince(header_value)), 81 "Referer" => request_headers.push(RequestHeader::Referer(header_value)), 82 "User-Agent" => request_headers.push(RequestHeader::UserAgent(header_value)), 83 84 // Entity Headers 85 "Allow" => { 86 let methods: Vec<RequestMethod> = header_value 87 .split(", ") 88 .map(|value| match value { 89 "GET" => RequestMethod::GET, 90 "HEAD" => RequestMethod::HEAD, 91 "POST" => RequestMethod::POST, 92 _ => RequestMethod::NotImplemented, 93 } 94 ).collect(); 95 96 entity_headers.push(EntityHeader::Allow(methods)) 97 } 98 99 "Content-Encoding" => entity_headers.push(EntityHeader::ContentEncoding(header_value)), 100 "Content-Length" => 101 if let Ok(length) = header_value.parse::<u128>() { 102 entity_headers.push(EntityHeader::ContentLength(length)); 103 } 104 105 "Content-Type" => entity_headers.push(EntityHeader::ContentType(header_value)), 106 "Expires" => entity_headers.push(EntityHeader::Expires(header_value)), 107 "Last-Modified" => entity_headers.push(EntityHeader::LastModified(header_value)), 108 109 // all the non-spec headers will be seen as entity headers :3 110 _ => entity_headers.push(EntityHeader::ExtensionHeader(header_name.to_string(), header_value)), // any unknown headers :D! 111 } 112 } 113 } 114 115 if request_line.method == RequestMethod::POST { 116 todo!() // hehe~ 117 } 118 119 http_1_0_request { 120 request_line, 121 general_headers, 122 request_headers, 123 entity_headers, 124 body: None, 125 } 126 } 127 128 129 pub(crate) fn return_response(request: Vec<String>, request_line: RequestLine) -> Polar<http_1_0_response> { 130 let request = parser(request, request_line); 131 132 let path_buf = match path_retriever(request.request_line.uri) { 133 Polar::Some(path_buf) => path_buf, 134 Polar::Silly(code) => return Polar::Silly(code), 135 }; 136 137 let metadata = match fs::metadata(&path_buf) { 138 Ok(data) => data, 139 Err(_) => return Polar::Silly(500), 140 }; 141 142 let file = match file_retriever(&path_buf) { 143 Polar::Some(file) => file, 144 Polar::Silly(code) => return Polar::Silly(code), 145 }; 146 147 148 let general_headers: Vec<GeneralHeader> = vec![ 149 GeneralHeader::Date({ 150 let now: DateTime<Utc> = Utc::now(); // Get the current UTC time 151 now.format("%a, %d %b %Y %H:%M:%S GMT").to_string() // Format da time as HTTP-date 152 }), 153 GeneralHeader::Pragma("no-cache".to_string()), 154 ]; 155 156 let response_headers: Vec<ResponseHeader> = vec![ 157 //ResponseHeader::Location(), 158 ResponseHeader::Server("PolarBear".to_string()), 159 //ResponseHeader::WWWAuthenticate(), 160 ]; 161 162 163 let entity_headers: Vec<EntityHeader> = vec![ 164 EntityHeader::Allow(vec![RequestMethod::GET]), 165 //EntityHeader::ContentEncoding() 166 EntityHeader::ContentLength(file.len() as u128), 167 EntityHeader::ContentType( 168 //todo! change this to metadata? maybe? 169 match path_buf.extension().and_then(|ext| ext.to_str()) { 170 None => { "text/plain; charset=utf-8".to_string() } 171 Some(extension) => match extension { 172 "html" => "text/html; charset=utf-8".to_string(), 173 "css" => "text/css; charset=utf-8".to_string(), 174 "js" => "text/javascript; charset=utf-8".to_string(), 175 "txt" => "text/plain; charset=utf-8".to_string(), 176 177 "png" => "image/png".to_string(), 178 "apng" => "image/apng".to_string(), 179 "avif" => "image/avif".to_string(), 180 "gif" => "image/gif".to_string(), 181 "jpeg" => "image/jpeg".to_string(), 182 "jpg" => "image/jpeg".to_string(), 183 "svg" => "image/svg+xml".to_string(), 184 "webp" => "image/webp".to_string(), 185 186 "mp4" => "video/mp4".to_string(), 187 "av1" => "video/AV1".to_string(), 188 189 _ => "text/plain; charset=utf-8".to_string(), 190 } 191 }), 192 //EntityHeader::Expires() 193 EntityHeader::LastModified(metadata 194 .modified() 195 .map(|time| { 196 let datetime: DateTime<Utc> = time.into(); // Convert `SystemTime` to the `DateTime<Utc>` 197 datetime.format("%a, %d %b %Y %H:%M:%S GMT").to_string() // Format the time in HTTP-date format 198 }) 199 .unwrap_or({ // Fallback to a default date 200 let now: DateTime<Utc> = Utc::now(); // Get the current UTC time 201 now.format("%a, %d %b %Y %H:%M:%S GMT").to_string() // Format da time as HTTP-date 202 }) 203 ), 204 ]; 205 206 207 Polar::Some(http_1_0_response { 208 status_line: StatusLine { 209 version: HttpVersion::HTTP_1_0, 210 status_code: 200, 211 reason_phrase: "OK".to_string(), 212 }, 213 general_headers, 214 response_headers, 215 entity_headers, 216 body: file, 217 }) 218 } 219 220 221 impl http_1_0_response { 222 pub(crate) fn into_response(mut self) -> Vec<u8> { 223 let mut response: Vec<u8> = vec![]; 224 // status line 225 response.append(&mut format!("HTTP/1.0 {} {} \r\n", self.status_line.status_code, self.status_line.reason_phrase).into_bytes()); 226 227 // da headers 228 for general_header in self.general_headers { 229 match general_header { 230 GeneralHeader::Date(data) => response.append(&mut format!("Date: {}\r\n", data).into_bytes()), 231 GeneralHeader::Pragma(data) => response.append(&mut format!("Pragma: {}\r\n", data).into_bytes()), 232 } 233 } 234 235 for response_header in self.response_headers { 236 match response_header { 237 ResponseHeader::Location(data) => response.append(&mut format!("Location: {}\r\n", data).into_bytes()), 238 ResponseHeader::Server(data) => response.append(&mut format!("Server: {}\r\n", data).into_bytes()), 239 ResponseHeader::WWWAuthenticate(data) => response.append(&mut format!("WWW-Authenticate: {}\r\n", data).into_bytes()), 240 } 241 } 242 243 for entity_header in self.entity_headers { 244 match entity_header { 245 EntityHeader::Allow(data) => { 246 let data: String = data 247 .iter() 248 .map(|value| match value { 249 RequestMethod::GET => "GET".to_string(), 250 RequestMethod::HEAD => "HEAD".to_string(), 251 RequestMethod::POST => "POST".to_string(), 252 RequestMethod::NotImplemented => "".to_string(), 253 }) 254 .collect::<Vec<String>>() 255 .join(", "); 256 257 response.append(&mut format!("Allow: {}\r\n", data).into_bytes()) 258 } 259 EntityHeader::ContentEncoding(data) => response.append(&mut format!("Content-Encoding: {}\r\n", data).into_bytes()), //todo: make modular since multiple can be defined 260 EntityHeader::ContentLength(data) => response.append(&mut format!("Content-Length: {}\r\n", data).into_bytes()), 261 EntityHeader::ContentType(data) => response.append(&mut format!("Content-Type: {}\r\n", data).into_bytes()), 262 EntityHeader::Expires(data) => response.append(&mut format!("Expires: {}\r\n", data).into_bytes()), 263 EntityHeader::LastModified(data) => response.append(&mut format!("Last-Modified: {}\r\n", data).into_bytes()), 264 EntityHeader::ExtensionHeader(_, _) => {} // todo: will not be processed, should prob change this but whatever 265 } 266 } 267 response.append(&mut "\r\n".to_string().into_bytes()); 268 // body 269 response.append(&mut self.body); 270 271 // return response :3 272 response 273 } 274 } 275 276 277