1use std::fs::File;
5use std::str::from_utf8;
6
7use crate::common::ascii::{CR, CRLF_LEN, LF, SP};
8pub use crate::common::HttpHeaderError;
9pub use crate::common::RequestError;
10use crate::common::{Body, Method, Version};
11use crate::headers::Headers;
12
13type RequestLineParts<'a> = (&'a [u8], &'a [u8], &'a [u8]);
15
16pub(crate) fn find(bytes: &[u8], sequence: &[u8]) -> Option<usize> {
21 bytes
22 .windows(sequence.len())
23 .position(|window| window == sequence)
24}
25
26#[derive(Clone, Debug, Eq, PartialEq)]
30pub struct Uri {
31 string: String,
32}
33
34impl Uri {
35 fn new(slice: &str) -> Self {
36 Self {
37 string: String::from(slice),
38 }
39 }
40
41 fn try_from(bytes: &[u8]) -> Result<Self, RequestError> {
42 if bytes.is_empty() {
43 return Err(RequestError::InvalidUri("Empty URI not allowed."));
44 }
45 let utf8_slice =
46 from_utf8(bytes).map_err(|_| RequestError::InvalidUri("Cannot parse URI as UTF-8."))?;
47 Ok(Self::new(utf8_slice))
48 }
49
50 pub fn get_abs_path(&self) -> &str {
62 const HTTP_SCHEME_PREFIX: &str = "http://";
63
64 if self.string.starts_with(HTTP_SCHEME_PREFIX) {
65 let without_scheme = &self.string[HTTP_SCHEME_PREFIX.len()..];
67 if without_scheme.is_empty() {
68 return "";
69 }
70 match without_scheme.bytes().position(|byte| byte == b'/') {
73 Some(len) => &without_scheme[len..],
75 None => "",
76 }
77 } else {
78 if self.string.starts_with('/') {
79 return self.string.as_str();
80 }
81
82 ""
83 }
84 }
85}
86
87#[derive(Debug, Eq, PartialEq)]
89pub struct RequestLine {
90 method: Method,
91 uri: Uri,
92 http_version: Version,
93}
94
95impl RequestLine {
96 fn parse_request_line(
97 request_line: &[u8],
98 ) -> std::result::Result<RequestLineParts, RequestError> {
99 if let Some(method_end) = find(request_line, &[SP]) {
100 let method = &request_line[..method_end];
102
103 let uri_start = method_end.checked_add(1).ok_or(RequestError::Overflow)?;
105
106 let uri_and_version = &request_line[uri_start..];
109
110 if let Some(uri_end) = find(uri_and_version, &[SP]) {
111 let uri = &uri_and_version[..uri_end];
113
114 let version_start = uri_end.checked_add(1).ok_or(RequestError::Overflow)?;
116
117 let version = &uri_and_version[version_start..];
119
120 return Ok((method, uri, version));
121 }
122 }
123
124 Err(RequestError::InvalidRequest)
126 }
127
128 pub fn try_from(request_line: &[u8]) -> Result<Self, RequestError> {
135 let (method, uri, version) = Self::parse_request_line(request_line)?;
136
137 Ok(Self {
138 method: Method::try_from(method)?,
139 uri: Uri::try_from(uri)?,
140 http_version: Version::try_from(version)?,
141 })
142 }
143
144 fn min_len() -> usize {
148 Method::Get.raw().len() + 1 + Version::Http10.raw().len() + 2
150 }
151}
152
153#[derive(Debug)]
155pub struct Request {
156 pub request_line: RequestLine,
158 pub headers: Headers,
160 pub body: Option<Body>,
162 pub files: Vec<File>,
164}
165
166impl Request {
167 pub fn try_from(byte_stream: &[u8], max_len: Option<usize>) -> Result<Self, RequestError> {
192 if let Some(limit) = max_len {
194 if byte_stream.len() >= limit {
195 return Err(RequestError::InvalidRequest);
196 }
197 }
198
199 let request_line_end = match find(byte_stream, &[CR, LF]) {
201 Some(len) => len,
202 None => return Err(RequestError::InvalidRequest),
204 };
205
206 let request_line_bytes = &byte_stream[..request_line_end];
208 if request_line_bytes.len() < RequestLine::min_len() {
209 return Err(RequestError::InvalidRequest);
210 }
211
212 let request_line = RequestLine::try_from(request_line_bytes)?;
213
214 match find(&byte_stream[request_line_end..], &[CR, LF, CR, LF]) {
217 Some(0) => Ok(Self {
220 request_line,
221 headers: Headers::default(),
222 body: None,
223 files: Vec::new(),
224 }),
225 Some(headers_end) => {
226 let headers_start = request_line_end + CRLF_LEN;
231 let headers_and_body = &byte_stream[headers_start..];
234 let headers_end = headers_end - CRLF_LEN;
240 let headers = Headers::try_from(&headers_and_body[..headers_end])?;
243
244 let body = match headers.content_length() {
247 0 => {
248 None
250 }
251 content_length => {
252 if request_line.method == Method::Get {
253 return Err(RequestError::InvalidRequest);
254 }
255 let crlf_end = headers_end + 2 * CRLF_LEN;
259 let body_len = headers_and_body.len() - crlf_end;
261 if body_len < content_length as usize {
264 return Err(RequestError::InvalidRequest);
265 }
266 let body_as_bytes = &headers_and_body[crlf_end..];
269 if body_as_bytes.len() == content_length as usize {
272 Some(Body::new(body_as_bytes))
273 } else {
274 return Err(RequestError::InvalidRequest);
275 }
276 }
277 };
278
279 Ok(Self {
280 request_line,
281 headers,
282 body,
283 files: Vec::new(),
284 })
285 }
286 None => Err(RequestError::InvalidRequest),
289 }
290 }
291
292 pub fn uri(&self) -> &Uri {
296 &self.request_line.uri
297 }
298
299 pub fn http_version(&self) -> Version {
301 self.request_line.http_version
302 }
303
304 pub fn method(&self) -> Method {
306 self.request_line.method
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 impl PartialEq for Request {
315 fn eq(&self, other: &Self) -> bool {
316 self.request_line == other.request_line
318 && self.headers.content_length() == other.headers.content_length()
319 && self.headers.expect() == other.headers.expect()
320 && self.headers.chunked() == other.headers.chunked()
321 }
322 }
323
324 impl RequestLine {
325 pub fn new(method: Method, uri: &str, http_version: Version) -> Self {
326 Self {
327 method,
328 uri: Uri::new(uri),
329 http_version,
330 }
331 }
332 }
333
334 #[test]
335 fn test_uri() {
336 for tc in &vec![
337 ("http://localhost/home", "/home"),
338 ("http://localhost:8080/home", "/home"),
339 ("http://localhost/home/sub", "/home/sub"),
340 ("/home", "/home"),
341 ("home", ""),
342 ("http://", ""),
343 ("http://192.168.0.0", ""),
344 ] {
345 assert_eq!(Uri::new(tc.0).get_abs_path(), tc.1);
346 }
347 }
348
349 #[test]
350 fn test_find() {
351 let bytes: &[u8; 13] = b"abcacrgbabsjl";
352
353 for tc in &vec![
354 ("ac", Some(3)),
355 ("rgb", Some(5)),
356 ("ab", Some(0)),
357 ("l", Some(12)),
358 ("abcacrgbabsjl", Some(0)),
359 ("jle", None),
360 ("asdkjhasjhdjhgsadg", None),
361 ] {
362 assert_eq!(find(&bytes[..], tc.0.as_bytes()), tc.1);
363 }
364 }
365
366 #[test]
367 fn test_into_request_line() {
368 let expected_request_line = RequestLine {
369 http_version: Version::Http10,
370 method: Method::Get,
371 uri: Uri::new("http://localhost/home"),
372 };
373
374 let request_line = b"GET http://localhost/home HTTP/1.0";
375 assert_eq!(
376 RequestLine::try_from(request_line).unwrap(),
377 expected_request_line
378 );
379
380 let expected_request_line = RequestLine {
381 http_version: Version::Http11,
382 method: Method::Get,
383 uri: Uri::new("http://localhost/home"),
384 };
385
386 let request_line = b"GET http://localhost/home HTTP/1.1";
388 assert_eq!(
389 RequestLine::try_from(request_line).unwrap(),
390 expected_request_line
391 );
392
393 let request_line = b"GET http://localhost/home HTTP/1.1";
395 assert_eq!(
396 RequestLine::try_from(request_line).unwrap(),
397 expected_request_line
398 );
399
400 let request_line = b"GET";
402 assert_eq!(
403 RequestLine::try_from(request_line).unwrap_err(),
404 RequestError::InvalidRequest
405 );
406
407 let request_line = b"CONNECT http://localhost/home HTTP/1.0";
409 assert_eq!(
410 RequestLine::try_from(request_line).unwrap_err(),
411 RequestError::InvalidHttpMethod("Unsupported HTTP method.")
412 );
413
414 let expected_request_line = RequestLine {
416 http_version: Version::Http10,
417 method: Method::Post,
418 uri: Uri::new("http://localhost/home"),
419 };
420 let request_line = b"POST http://localhost/home HTTP/1.0";
421 assert_eq!(
422 RequestLine::try_from(request_line).unwrap(),
423 expected_request_line,
424 );
425
426 let request_line = b"GET HTTP/1.0";
428 assert_eq!(
429 RequestLine::try_from(request_line).unwrap_err(),
430 RequestError::InvalidUri("Empty URI not allowed.")
431 );
432
433 let request_line = b"GET http://localhost/home HTTP/2.0";
435 assert_eq!(
436 RequestLine::try_from(request_line).unwrap_err(),
437 RequestError::InvalidHttpVersion("Unsupported HTTP version.")
438 );
439
440 let request_line = b"nothing";
442 assert_eq!(
443 RequestLine::try_from(request_line).unwrap_err(),
444 RequestError::InvalidRequest
445 );
446
447 let request_line = b"GET /";
449 assert_eq!(
450 RequestLine::try_from(request_line).unwrap_err(),
451 RequestError::InvalidRequest
452 );
453 }
454
455 #[test]
456 fn test_into_request() {
457 let expected_request = Request {
458 request_line: RequestLine {
459 http_version: Version::Http10,
460 method: Method::Get,
461 uri: Uri::new("http://localhost/home"),
462 },
463 body: None,
464 files: Vec::new(),
465 headers: Headers::default(),
466 };
467 let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\
468 Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n";
469 let request = Request::try_from(request_bytes, None).unwrap();
470 assert_eq!(request, expected_request);
471 assert_eq!(request.uri(), &Uri::new("http://localhost/home"));
472 assert_eq!(request.http_version(), Version::Http10);
473 assert!(request.body.is_none());
474
475 let request_bytes = b"GET / HTTP/1.1";
477 assert_eq!(
478 Request::try_from(request_bytes, None).unwrap_err(),
479 RequestError::InvalidRequest
480 );
481
482 let request_bytes = b"GET";
484 assert_eq!(
485 Request::try_from(request_bytes, None).unwrap_err(),
486 RequestError::InvalidRequest
487 );
488
489 let request_bytes = b"GET /machine-config HTTP/1.1\r\n\
491 Content-Length: 13\r\n\
492 Content-Type: application/json\r\n\r\nwhatever body";
493 assert_eq!(
494 Request::try_from(request_bytes, None).unwrap_err(),
495 RequestError::InvalidRequest
496 );
497
498 let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\
500 Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n";
501 assert_eq!(
502 Request::try_from(request_bytes, Some(20)).unwrap_err(),
503 RequestError::InvalidRequest
504 );
505
506 let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\
508 Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n";
509 assert!(Request::try_from(request_bytes, Some(500)).is_ok());
510
511 let request_bytes = b"PATCH http://localhost/home HTTP/1.1\r\n\
513 Expect: 100-continue\r\n\
514 Transfer-Encoding: chunked\r\n\
515 Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody";
516 let request = Request::try_from(request_bytes, None).unwrap();
517 assert_eq!(request.uri(), &Uri::new("http://localhost/home"));
518 assert_eq!(request.http_version(), Version::Http11);
519 assert_eq!(request.method(), Method::Patch);
520 assert!(request.headers.chunked());
521 assert!(request.headers.expect());
522 assert_eq!(request.headers.content_length(), 26);
523 assert_eq!(
524 request.body.unwrap().body,
525 String::from("this is not\n\r\na json \nbody")
526 .as_bytes()
527 .to_vec()
528 );
529
530 Request::try_from(b"PATCH http://localhost/home HTTP/1.1\r\n", None).unwrap_err();
532
533 let request_bytes = b"PATCH http://localhost/home HTTP/1.1\r\n\
535 Expect: 100-continue\r\n\
536 Transfer-Encoding: identity; q=0\r\n\
537 Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody";
538
539 assert!(Request::try_from(request_bytes, None).is_ok());
540
541 let request_bytes = b"PATCH http://localhost/home HTTP/1.1\r\n\
543 Content-Length: 5000\r\n\r\nthis is a short body";
544 let request = Request::try_from(request_bytes, None).unwrap_err();
545 assert_eq!(request, RequestError::InvalidRequest);
546
547 let request_bytes = b"GET http://localhost/ HTTP/1.0\r\n\
549 Accept-Encoding: gzip\r\n\r\n";
550 let request = Request::try_from(request_bytes, None).unwrap();
551 assert_eq!(request.uri(), &Uri::new("http://localhost/"));
552 assert_eq!(request.http_version(), Version::Http10);
553 assert_eq!(request.method(), Method::Get);
554 assert!(!request.headers.chunked());
555 assert!(!request.headers.expect());
556 assert_eq!(request.headers.content_length(), 0);
557 assert!(request.body.is_none());
558
559 let request_bytes = b"GET http://localhost/ HTTP/1.0\r\n\
560 Accept-Encoding: identity;q=0\r\n\r\n";
561 let request = Request::try_from(request_bytes, None);
562 assert_eq!(
563 request.unwrap_err(),
564 RequestError::HeaderError(HttpHeaderError::InvalidValue(
565 "Accept-Encoding".to_string(),
566 "identity;q=0".to_string()
567 ))
568 );
569 }
570}