dbs_uhttp/
request.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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
13// This type represents the RequestLine raw parts: method, uri and version.
14type RequestLineParts<'a> = (&'a [u8], &'a [u8], &'a [u8]);
15
16/// Finds the first occurrence of `sequence` in the `bytes` slice.
17///
18/// Returns the starting position of the `sequence` in `bytes` or `None` if the
19/// `sequence` is not found.
20pub(crate) fn find(bytes: &[u8], sequence: &[u8]) -> Option<usize> {
21    bytes
22        .windows(sequence.len())
23        .position(|window| window == sequence)
24}
25
26/// Wrapper over HTTP URIs.
27///
28/// The `Uri` can not be used directly and it is only accessible from an HTTP Request.
29#[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    /// Returns the absolute path of the `Uri`.
51    ///
52    /// URIs can be represented in absolute form or relative form. The absolute form includes
53    /// the HTTP scheme, followed by the absolute path as follows:
54    /// "http:" "//" host [ ":" port ] [ abs_path ]
55    /// The relative URIs can be one of net_path | abs_path | rel_path.
56    /// This method only handles absolute URIs and relative URIs specified by abs_path.
57    /// The abs_path is expected to start with '/'.
58    ///
59    /// # Errors
60    /// Returns an empty byte array when the host or the path are empty/invalid.
61    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            // Slice access is safe because we checked above that `self.string` size <= `HTTP_SCHEME_PREFIX.len()`.
66            let without_scheme = &self.string[HTTP_SCHEME_PREFIX.len()..];
67            if without_scheme.is_empty() {
68                return "";
69            }
70            // The host in this case includes the port and contains the bytes after http:// up to
71            // the next '/'.
72            match without_scheme.bytes().position(|byte| byte == b'/') {
73                // Slice access is safe because `position` validates that `len` is a valid index.
74                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/// Wrapper over an HTTP Request Line.
88#[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            // The slice access is safe because `find` validates that `method_end` < `request_line` size.
101            let method = &request_line[..method_end];
102
103            // `uri_start` <= `request_line` size.
104            let uri_start = method_end.checked_add(1).ok_or(RequestError::Overflow)?;
105
106            // Slice access is safe because `uri_start` <= `request_line` size.
107            // If `uri_start` == `request_line` size, then `uri_and_version` will be an empty slice.
108            let uri_and_version = &request_line[uri_start..];
109
110            if let Some(uri_end) = find(uri_and_version, &[SP]) {
111                // Slice access is safe because `find` validates that `uri_end` < `uri_and_version` size.
112                let uri = &uri_and_version[..uri_end];
113
114                // `version_start` <= `uri_and_version` size.
115                let version_start = uri_end.checked_add(1).ok_or(RequestError::Overflow)?;
116
117                // Slice access is safe because `version_start` <= `uri_and_version` size.
118                let version = &uri_and_version[version_start..];
119
120                return Ok((method, uri, version));
121            }
122        }
123
124        // Request Line can be valid only if it contains the method, uri and version separated with SP.
125        Err(RequestError::InvalidRequest)
126    }
127
128    /// Tries to parse a byte stream in a request line. Fails if the request line is malformed.
129    ///
130    /// # Errors
131    /// `InvalidHttpMethod` is returned if the specified HTTP method is unsupported.
132    /// `InvalidHttpVersion` is returned if the specified HTTP version is unsupported.
133    /// `InvalidUri` is returned if the specified Uri is not valid.
134    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    // Returns the minimum length of a valid request. The request must contain
145    // the method (GET), the URI (minimum 1 character), the HTTP version(HTTP/DIGIT.DIGIT) and
146    // 2 separators (SP).
147    fn min_len() -> usize {
148        // Addition is safe because these are small constants.
149        Method::Get.raw().len() + 1 + Version::Http10.raw().len() + 2
150    }
151}
152
153/// Wrapper over an HTTP Request.
154#[derive(Debug)]
155pub struct Request {
156    /// The request line of the request.
157    pub request_line: RequestLine,
158    /// The headers of the request.
159    pub headers: Headers,
160    /// The body of the request.
161    pub body: Option<Body>,
162    /// The optional files associated with the request.
163    pub files: Vec<File>,
164}
165
166impl Request {
167    /// Parses a byte slice into a HTTP Request.
168    ///
169    /// The byte slice is expected to have the following format: </br>
170    ///     * Request Line: "GET SP Request-uri SP HTTP/1.0 CRLF" - Mandatory </br>
171    ///     * Request Headers "<headers> CRLF"- Optional </br>
172    ///     * Empty Line "CRLF" </br>
173    ///     * Entity Body - Optional </br>
174    /// The request headers and the entity body are not parsed and None is returned because
175    /// these are not used by the MMDS server.
176    /// The only supported method is GET and the HTTP protocol is expected to be HTTP/1.0
177    /// or HTTP/1.1.
178    ///
179    /// # Errors
180    /// The function returns InvalidRequest when parsing the byte stream fails.
181    ///
182    /// # Examples
183    ///
184    /// ```
185    /// use dbs_uhttp::Request;
186    ///
187    /// let max_request_len = 2000;
188    /// let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\r\n";
189    /// let http_request = Request::try_from(request_bytes, Some(max_request_len)).unwrap();
190    /// ```
191    pub fn try_from(byte_stream: &[u8], max_len: Option<usize>) -> Result<Self, RequestError> {
192        // If a size limit is provided, verify the request length does not exceed it.
193        if let Some(limit) = max_len {
194            if byte_stream.len() >= limit {
195                return Err(RequestError::InvalidRequest);
196            }
197        }
198
199        // The first line of the request is the Request Line. The line ending is CR LF.
200        let request_line_end = match find(byte_stream, &[CR, LF]) {
201            Some(len) => len,
202            // If no CR LF is found in the stream, the request format is invalid.
203            None => return Err(RequestError::InvalidRequest),
204        };
205
206        // Slice access is safe because `find` validates that `request_line_end` < `byte_stream` size.
207        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        // Find the next CR LF CR LF sequence in our buffer starting at the end on the Request
215        // Line, including the trailing CR LF previously found.
216        match find(&byte_stream[request_line_end..], &[CR, LF, CR, LF]) {
217            // If we have found a CR LF CR LF at the end of the Request Line, the request
218            // is complete.
219            Some(0) => Ok(Self {
220                request_line,
221                headers: Headers::default(),
222                body: None,
223                files: Vec::new(),
224            }),
225            Some(headers_end) => {
226                // Parse the request headers.
227                // Start by removing the leading CR LF from them.
228                // The addition is safe because `find()` guarantees that `request_line_end`
229                // precedes 2 `CRLF` sequences.
230                let headers_start = request_line_end + CRLF_LEN;
231                // Slice access is safe because starting from `request_line_end` there are at least two CRLF
232                // (enforced by `find` at the start of this method).
233                let headers_and_body = &byte_stream[headers_start..];
234                // Because we advanced the start with CRLF_LEN, we now have to subtract CRLF_LEN
235                // from the end in order to keep the same window.
236                // Underflow is not possible here because `byte_stream[request_line_end..]` starts with CR LF,
237                // so `headers_end` can be either zero (this case is treated separately in the first match arm)
238                // or >= 3 (current case).
239                let headers_end = headers_end - CRLF_LEN;
240                // Slice access is safe because `headers_end` is checked above
241                // (`find` gives a valid position, and  subtracting 2 can't underflow).
242                let headers = Headers::try_from(&headers_and_body[..headers_end])?;
243
244                // Parse the body of the request.
245                // Firstly check if we have a body.
246                let body = match headers.content_length() {
247                    0 => {
248                        // No request body.
249                        None
250                    }
251                    content_length => {
252                        if request_line.method == Method::Get {
253                            return Err(RequestError::InvalidRequest);
254                        }
255                        // Multiplication is safe because `CRLF_LEN` is a small constant.
256                        // Addition is also safe because `headers_end` started out as the result
257                        // of `find(<something>, CRLFCRLF)`, then `CRLF_LEN` was subtracted from it.
258                        let crlf_end = headers_end + 2 * CRLF_LEN;
259                        // This can't underflow because `headers_and_body.len()` >= `crlf_end`.
260                        let body_len = headers_and_body.len() - crlf_end;
261                        // Headers suggest we have a body, but the buffer is shorter than the specified
262                        // content length.
263                        if body_len < content_length as usize {
264                            return Err(RequestError::InvalidRequest);
265                        }
266                        // Slice access is safe because `crlf_end` is the index after two CRLF
267                        // (it is <= `headers_and_body` size).
268                        let body_as_bytes = &headers_and_body[crlf_end..];
269                        // If the actual length of the body is different than the `Content-Length` value
270                        // in the headers, then this request is invalid.
271                        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            // If we can't find a CR LF CR LF even though the request should have headers
287            // the request format is invalid.
288            None => Err(RequestError::InvalidRequest),
289        }
290    }
291
292    /// Returns the `Uri` from the parsed `Request`.
293    ///
294    /// The return value can be used to get the absolute path of the URI.
295    pub fn uri(&self) -> &Uri {
296        &self.request_line.uri
297    }
298
299    /// Returns the HTTP `Version` of the `Request`.
300    pub fn http_version(&self) -> Version {
301        self.request_line.http_version
302    }
303
304    /// Returns the HTTP `Method` of the `Request`.
305    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            // Ignore the other fields of Request for now because they are not used.
317            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        // Happy case with request line ending in CRLF.
387        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        // Happy case with request line ending in LF instead of CRLF.
394        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        // Test for invalid request missing the separator.
401        let request_line = b"GET";
402        assert_eq!(
403            RequestLine::try_from(request_line).unwrap_err(),
404            RequestError::InvalidRequest
405        );
406
407        // Test for invalid method.
408        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        // POST method was supported
415        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        // Test for invalid uri.
427        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        // Test for invalid HTTP version.
434        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        // Test for invalid format with no method, uri or version.
441        let request_line = b"nothing";
442        assert_eq!(
443            RequestLine::try_from(request_line).unwrap_err(),
444            RequestError::InvalidRequest
445        );
446
447        // Test for invalid format with no version.
448        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        // Test for invalid Request (missing CR LF).
476        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        // Test for invalid Request (length is less than minimum).
483        let request_bytes = b"GET";
484        assert_eq!(
485            Request::try_from(request_bytes, None).unwrap_err(),
486            RequestError::InvalidRequest
487        );
488
489        // Test for invalid Request (`GET` requests should have no body).
490        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        // Test for request larger than maximum len provided.
499        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        // Test request smaller than maximum len provided is ok.
507        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        // Test for a request with the headers we are looking for.
512        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        // Test for an invalid request format.
531        Request::try_from(b"PATCH http://localhost/home HTTP/1.1\r\n", None).unwrap_err();
532
533        // Test for an invalid encoding.
534        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        // Test for an invalid content length.
542        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        // Test for a request without a body and an optional header.
548        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}