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 {
176 [METHOD.get, METHOD.head, METHOD.post, METHOD.put, METHOD.delete, METHOD.connect, METHOD.options, METHOD.trace, METHOD.patch, Request::PURGE]
177 .iter()
178 .any(|m| m.eq_ignore_ascii_case(method))
179 }
180
181 pub const PURGE: &'static str = "PURGE";
185
186 pub fn method_list() -> Vec<String> {
187 let method_get = METHOD.get.to_string();
188 let method_head = METHOD.head.to_string();
189 let method_post = METHOD.post.to_string();
190 let method_put = METHOD.put.to_string();
191 let method_delete = METHOD.delete.to_string();
192 let method_connect = METHOD.connect.to_string();
193 let method_options = METHOD.options.to_string();
194 let method_trace = METHOD.trace.to_string();
195 let method_patch = METHOD.patch.to_string();
196
197 let method_list = vec![
198 method_get,
199 method_head,
200 method_post,
201 method_put,
202 method_delete,
203 method_connect,
204 method_options,
205 method_trace,
206 method_patch,
207 ];
208
209 method_list
210 }
211
212 pub fn generate(&self) -> Vec<u8> {
214 let clone = self.clone();
215 let mut request = Request::_generate_request(clone).as_bytes().to_vec();
216
217 request.extend_from_slice(&self.body);
218
219 request
220 }
221
222 pub fn generate_request(request: Request) -> String {
224 Request::_generate_request(request)
225 }
226
227 pub fn _generate_request(request: Request) -> String {
228 let status = [
229 request.method,
230 request.request_uri,
231 request.http_version,
232 SYMBOL.new_line_carriage_return.to_string()
233 ].join(SYMBOL.whitespace);
234
235 let mut headers = SYMBOL.empty_string.to_string();
236 for header in request.headers {
237 let mut header_string = SYMBOL.empty_string.to_string();
238 header_string.push_str(&header.name);
239 header_string.push_str(Header::NAME_VALUE_SEPARATOR);
240 header_string.push_str(&header.value);
241 header_string.push_str(SYMBOL.new_line_carriage_return);
242 headers.push_str(&header_string);
243 }
244
245 let request = format!(
246 "{}{}{}",
247 status,
248 headers,
249 SYMBOL.new_line_carriage_return
250 );
251
252
253 request
254 }
255
256 pub fn parse(request_vec_u8: &[u8]) -> Result<Request, String> {
257 Request::parse_request(request_vec_u8)
258 }
259
260 pub fn parse_request(request_vec_u8: &[u8]) -> Result<Request, String> {
261 let mut cursor = io::Cursor::new(request_vec_u8);
262
263 let mut request = Request {
264 method: "".to_string(),
265 request_uri: "".to_string(),
266 http_version: "".to_string(),
267 headers: vec![],
268 body: Default::default(),
269 };
270
271 let content_length: usize = 0;
272 let iteration_number : usize = 0;
273 return match Request::cursor_read(&mut cursor, iteration_number, &mut request, content_length) {
274 Ok(_) => {
275 Ok(request)
276 }
277 Err(error_message) => {
278 Err(error_message)
279 }
280 }
281
282 }
283
284
285 pub fn parse_method_and_request_uri_and_http_version_string(http_version_status_code_reason_phrase: &str) -> Result<(String, String, String), String> {
286 let lowercase_unparsed_method_and_request_uri_and_http_version = http_version_status_code_reason_phrase.trim();
287
288 let boxed_split_without_method = lowercase_unparsed_method_and_request_uri_and_http_version.split_once(SYMBOL.whitespace);
289 if boxed_split_without_method.is_none() {
290 return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
291 }
292
293 let (method, without_method) = boxed_split_without_method.unwrap();
294 if !Request::is_supported_method(method) {
295 return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
296 }
297
298 let boxed_without_method = without_method.split_once(SYMBOL.whitespace);
299 if boxed_without_method.is_none() {
300 return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
301 }
302
303 let (request_uri, http_version) = boxed_without_method.unwrap();
304 let request_uri = Request::normalize_request_target(request_uri)
305 .ok_or_else(|| Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())?;
306
307
308 if !HTTP::is_supported_version(http_version) {
309 return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
310 }
311
312 Ok((method.to_string(), request_uri, http_version.to_string()))
313
314 }
315
316 pub fn normalize_request_target(target: &str) -> Option<String> {
322 if target.starts_with('/') || target == "*" {
323 return Some(target.to_string());
324 }
325 let lower = target.to_ascii_lowercase();
326 let after_scheme = if lower.starts_with("http://") {
327 &target["http://".len()..]
328 } else if lower.starts_with("https://") {
329 &target["https://".len()..]
330 } else {
331 return None;
332 };
333 let path_start = after_scheme.find(|c| c == '/' || c == '?').unwrap_or(after_scheme.len());
334 if path_start == 0 {
335 return None; }
337 let rest = &after_scheme[path_start..];
338 Some(if rest.starts_with('/') { rest.to_string() } else { format!("/{}", rest) })
339 }
340
341 pub fn parse_http_request_header_string(header_string: &str) -> Header {
346 let line = header_string.trim_end_matches(['\r', '\n']);
347 match line.split_once(':') {
348 Some((name, value)) => Header {
349 name: Header::name_from(name),
350 value: value.trim_matches([' ', '\t']).to_string().into(),
351 },
352 None => Header { name: line.to_string().into(), value: String::new().into() },
353 }
354 }
355
356 pub fn cursor_read(cursor: &mut Cursor<&[u8]>, mut iteration_number: usize, request: &mut Request, content_length: usize) -> Result<bool, String> {
364 let _ = content_length;
365 let input: &[u8] = cursor.get_ref();
368 loop {
369 let start = (cursor.position() as usize).min(input.len());
370 let rest = &input[start..];
371 let line_len = rest.iter().position(|&b| b == b'\n').map_or(rest.len(), |i| i + 1);
372 cursor.set_position((start + line_len) as u64);
373 let string = std::str::from_utf8(&rest[..line_len]).map_err(|e| e.to_string())?;
374
375 let is_first_iteration = iteration_number == 0;
376 if is_first_iteration {
377 let (method, request_uri, http_version) =
378 Request::parse_method_and_request_uri_and_http_version_string(string)?;
379 request.method = method;
380 request.request_uri = request_uri;
381 request.http_version = http_version;
382 }
383
384 if line_len == 0 || string.trim().is_empty() {
386 break;
387 }
388
389 if !is_first_iteration {
393 let header = Request::parse_http_request_header_string(string);
394 if header.name.eq_ignore_ascii_case(Header::_CONTENT_LENGTH) {
395 let _: usize = header.value.trim().parse()
397 .map_err(|_| format!("invalid Content-Length: {:?}", header.value))?;
398 }
399 request.headers.push(header);
400 }
401 iteration_number += 1;
402 }
403
404 let start = (cursor.position() as usize).min(input.len());
406 request.body = input[start..].to_vec().into();
407 cursor.set_position(input.len() as u64);
408
409 Ok(true)
410 }
411
412}
413
414impl std::fmt::Display for Request {
415 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
416 write!(fmt, "Request method {} and request uri {} and http_version {}", self.method, self.request_uri, self.http_version)
417 }
418}