1#[cfg(test)]
13mod tests;
14#[cfg(test)]
15mod example;
16#[cfg(test)]
17mod reference_parser;
18
19use std::collections::HashMap;
20use std::io;
21use std::io::Cursor;
22use crate::header::Header;
23use crate::http::HTTP;
24use crate::symbol::SYMBOL;
25use crate::url::URL;
26
27mod body;
28pub use body::Body;
29
30#[derive(PartialEq, Eq, Clone, Debug)]
32pub struct Request {
33 pub method: String,
35 pub request_uri: String,
37 pub http_version: String,
39 pub headers: Vec<Header>,
41 pub body: Body,
46}
47
48#[derive(PartialEq, Eq, Clone, Debug)]
49pub struct Method {
50 pub get: &'static str,
51 pub head: &'static str,
52 pub post: &'static str,
53 pub put: &'static str,
54 pub delete: &'static str,
55 pub connect: &'static str,
56 pub options: &'static str,
57 pub trace: &'static str,
58 pub patch: &'static str,
59}
60
61pub const METHOD: Method = Method {
62 get: "GET",
63 head: "HEAD",
64 post: "POST",
65 put: "PUT",
66 delete: "DELETE",
67 connect: "CONNECT",
68 options: "OPTIONS",
69 trace: "TRACE",
70 patch: "PATCH",
71};
72
73impl Request {
74 pub const _ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION: &'static str = "Unable to parse method, request uri and http version";
75
76 pub fn get_header(&self, name: String) -> Option<&Header> {
79 self.header(&name)
80 }
81
82 pub fn header(&self, name: &str) -> Option<&Header> {
85 self.headers.iter().find(|h| h.name.eq_ignore_ascii_case(name))
86 }
87
88 pub fn get_domain(&self) -> Result<Option<String>, String> {
89 let boxed_host = &self.header(Header::_HOST);
90 if boxed_host.is_none() {
91 return Ok(None);
92 }
93
94 let host_header = boxed_host.unwrap();
95 let host = host_header.value.to_string();
96 if host.is_empty() {
97 return Ok(None); }
99
100 let boxed_host_port = host.split_once(&SYMBOL.colon.to_string());
101 if boxed_host_port.is_none() {
102 return Ok(Some(host))
103 }
104
105 let (domain, _port_str) = boxed_host_port.unwrap();
106
107 Ok(Some(domain.to_string()))
108 }
109
110 pub fn get_port(&self) -> Result<Option<i128>, String> {
111 let boxed_host = &self.header(Header::_HOST);
112 if boxed_host.is_none() {
113 return Ok(None);
114 }
115
116 let host_header = boxed_host.unwrap();
117 let host = host_header.value.to_string();
118
119 let boxed_host_port = host.split_once(&SYMBOL.colon.to_string());
120 if boxed_host_port.is_none() {
121 return Ok(None)
122 }
123
124 let (_domain, port_str) = boxed_host_port.unwrap();
125
126 let boxed_port_i128 = port_str.parse::<i128>();
127 if boxed_port_i128.is_err() {
128 let message = boxed_port_i128.err().unwrap().to_string();
129 return Err(message);
130 }
131
132 let port = boxed_port_i128.unwrap();
133
134 Ok(Some(port))
135 }
136
137 pub fn get_query(&self) -> Result<Option<HashMap<String, String>>, String> {
138 self.get_uri_query()
139 }
140
141 pub fn get_uri_query(&self) -> Result<Option<HashMap<String, String>>, String> {
142 let url_array = ["http://", "localhost/", &self.request_uri];
146 let url = url_array.join(SYMBOL.empty_string);
147
148 let boxed_url_components = URL::parse(&url);
149 if boxed_url_components.is_err() {
150 let message = boxed_url_components.err().unwrap().to_string();
151 return Err(message)
152 }
153 Ok(boxed_url_components.unwrap().query)
154 }
155
156 pub fn get_path(&self) -> Result<String, String> {
157 self.get_uri_path()
158 }
159
160 pub fn get_uri_path(&self) -> Result<String, String> {
161 let url = self.request_uri.to_string();
163
164 let boxed_url_components = crate::url::URL::parse_request_target(&url);
165 if boxed_url_components.is_err() {
166 let message = boxed_url_components.err().unwrap().to_string();
167 return Err(message)
168 }
169 Ok(boxed_url_components.unwrap().path)
170 }
171
172 pub fn is_supported_method(method: &str) -> bool {
175 [METHOD.get, METHOD.head, METHOD.post, METHOD.put, METHOD.delete, METHOD.connect, METHOD.options, METHOD.trace, METHOD.patch]
176 .iter()
177 .any(|m| m.eq_ignore_ascii_case(method))
178 }
179
180 pub fn method_list() -> Vec<String> {
181 let method_get = METHOD.get.to_string();
182 let method_head = METHOD.head.to_string();
183 let method_post = METHOD.post.to_string();
184 let method_put = METHOD.put.to_string();
185 let method_delete = METHOD.delete.to_string();
186 let method_connect = METHOD.connect.to_string();
187 let method_options = METHOD.options.to_string();
188 let method_trace = METHOD.trace.to_string();
189 let method_patch = METHOD.patch.to_string();
190
191 let method_list = vec![
192 method_get,
193 method_head,
194 method_post,
195 method_put,
196 method_delete,
197 method_connect,
198 method_options,
199 method_trace,
200 method_patch,
201 ];
202
203 method_list
204 }
205
206 pub fn generate(&self) -> Vec<u8> {
208 let clone = self.clone();
209 let mut request = Request::_generate_request(clone).as_bytes().to_vec();
210
211 request.extend_from_slice(&self.body);
212
213 request
214 }
215
216 pub fn generate_request(request: Request) -> String {
218 Request::_generate_request(request)
219 }
220
221 pub fn _generate_request(request: Request) -> String {
222 let status = [
223 request.method,
224 request.request_uri,
225 request.http_version,
226 SYMBOL.new_line_carriage_return.to_string()
227 ].join(SYMBOL.whitespace);
228
229 let mut headers = SYMBOL.empty_string.to_string();
230 for header in request.headers {
231 let mut header_string = SYMBOL.empty_string.to_string();
232 header_string.push_str(&header.name);
233 header_string.push_str(Header::NAME_VALUE_SEPARATOR);
234 header_string.push_str(&header.value);
235 header_string.push_str(SYMBOL.new_line_carriage_return);
236 headers.push_str(&header_string);
237 }
238
239 let request = format!(
240 "{}{}{}",
241 status,
242 headers,
243 SYMBOL.new_line_carriage_return
244 );
245
246
247 request
248 }
249
250 pub fn parse(request_vec_u8: &[u8]) -> Result<Request, String> {
251 Request::parse_request(request_vec_u8)
252 }
253
254 pub fn parse_request(request_vec_u8: &[u8]) -> Result<Request, String> {
255 let mut cursor = io::Cursor::new(request_vec_u8);
256
257 let mut request = Request {
258 method: "".to_string(),
259 request_uri: "".to_string(),
260 http_version: "".to_string(),
261 headers: vec![],
262 body: Default::default(),
263 };
264
265 let content_length: usize = 0;
266 let iteration_number : usize = 0;
267 return match Request::cursor_read(&mut cursor, iteration_number, &mut request, content_length) {
268 Ok(_) => {
269 Ok(request)
270 }
271 Err(error_message) => {
272 Err(error_message)
273 }
274 }
275
276 }
277
278
279 pub fn parse_method_and_request_uri_and_http_version_string(http_version_status_code_reason_phrase: &str) -> Result<(String, String, String), String> {
280 let lowercase_unparsed_method_and_request_uri_and_http_version = http_version_status_code_reason_phrase.trim();
281
282 let boxed_split_without_method = lowercase_unparsed_method_and_request_uri_and_http_version.split_once(SYMBOL.whitespace);
283 if boxed_split_without_method.is_none() {
284 return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
285 }
286
287 let (method, without_method) = boxed_split_without_method.unwrap();
288 if !Request::is_supported_method(method) {
289 return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
290 }
291
292 let boxed_without_method = without_method.split_once(SYMBOL.whitespace);
293 if boxed_without_method.is_none() {
294 return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
295 }
296
297 let (request_uri, http_version) = boxed_without_method.unwrap();
298 let request_uri = Request::normalize_request_target(request_uri)
299 .ok_or_else(|| Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())?;
300
301
302 if !HTTP::is_supported_version(http_version) {
303 return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
304 }
305
306 Ok((method.to_string(), request_uri, http_version.to_string()))
307
308 }
309
310 pub fn normalize_request_target(target: &str) -> Option<String> {
316 if target.starts_with('/') || target == "*" {
317 return Some(target.to_string());
318 }
319 let lower = target.to_ascii_lowercase();
320 let after_scheme = if lower.starts_with("http://") {
321 &target["http://".len()..]
322 } else if lower.starts_with("https://") {
323 &target["https://".len()..]
324 } else {
325 return None;
326 };
327 let path_start = after_scheme.find(|c| c == '/' || c == '?').unwrap_or(after_scheme.len());
328 if path_start == 0 {
329 return None; }
331 let rest = &after_scheme[path_start..];
332 Some(if rest.starts_with('/') { rest.to_string() } else { format!("/{}", rest) })
333 }
334
335 pub fn parse_http_request_header_string(header_string: &str) -> Header {
340 let line = header_string.trim_end_matches(['\r', '\n']);
341 match line.split_once(':') {
342 Some((name, value)) => Header {
343 name: Header::name_from(name),
344 value: value.trim_matches([' ', '\t']).to_string().into(),
345 },
346 None => Header { name: line.to_string().into(), value: String::new().into() },
347 }
348 }
349
350 pub fn cursor_read(cursor: &mut Cursor<&[u8]>, mut iteration_number: usize, request: &mut Request, content_length: usize) -> Result<bool, String> {
358 let _ = content_length;
359 let input: &[u8] = cursor.get_ref();
362 loop {
363 let start = (cursor.position() as usize).min(input.len());
364 let rest = &input[start..];
365 let line_len = rest.iter().position(|&b| b == b'\n').map_or(rest.len(), |i| i + 1);
366 cursor.set_position((start + line_len) as u64);
367 let string = std::str::from_utf8(&rest[..line_len]).map_err(|e| e.to_string())?;
368
369 let is_first_iteration = iteration_number == 0;
370 if is_first_iteration {
371 let (method, request_uri, http_version) =
372 Request::parse_method_and_request_uri_and_http_version_string(string)?;
373 request.method = method;
374 request.request_uri = request_uri;
375 request.http_version = http_version;
376 }
377
378 if line_len == 0 || string.trim().is_empty() {
380 break;
381 }
382
383 if !is_first_iteration {
387 let header = Request::parse_http_request_header_string(string);
388 if header.name.eq_ignore_ascii_case(Header::_CONTENT_LENGTH) {
389 let _: usize = header.value.trim().parse()
391 .map_err(|_| format!("invalid Content-Length: {:?}", header.value))?;
392 }
393 request.headers.push(header);
394 }
395 iteration_number += 1;
396 }
397
398 let start = (cursor.position() as usize).min(input.len());
400 request.body = input[start..].to_vec().into();
401 cursor.set_position(input.len() as u64);
402
403 Ok(true)
404 }
405
406}
407
408impl std::fmt::Display for Request {
409 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
410 write!(fmt, "Request method {} and request uri {} and http_version {}", self.method, self.request_uri, self.http_version)
411 }
412}