Coverage Report

Created: 2024-05-21 06:19

/rust/git/checkouts/micro-http-22be4cdcbef12607/ef43cef/src/request.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
// SPDX-License-Identifier: Apache-2.0
3
4
use std::fs::File;
5
use std::str::from_utf8;
6
7
use crate::common::ascii::{CR, CRLF_LEN, LF, SP};
8
9
pub use crate::common::RequestError;
10
use crate::common::{Body, Method, Version};
11
use crate::headers::Headers;
12
13
// This type represents the RequestLine raw parts: method, uri and version.
14
type 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.
20
69.4k
pub(crate) fn find(bytes: &[u8], sequence: &[u8]) -> Option<usize> {
21
69.4k
    bytes
22
69.4k
        .windows(sequence.len())
23
11.1M
        .position(|window| window == sequence)
24
69.4k
}
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
0
#[derive(Clone, Debug, PartialEq, Eq)]
30
pub struct Uri {
31
    string: String,
32
}
33
34
impl Uri {
35
17.3k
    fn new(slice: &str) -> Self {
36
17.3k
        Self {
37
17.3k
            string: String::from(slice),
38
17.3k
        }
39
17.3k
    }
40
41
17.3k
    fn try_from(bytes: &[u8]) -> Result<Self, RequestError> {
42
17.3k
        if bytes.is_empty() {
43
0
            return Err(RequestError::InvalidUri("Empty URI not allowed."));
44
17.3k
        }
45
17.3k
        let utf8_slice =
46
17.3k
            from_utf8(bytes).map_err(|_| RequestError::InvalidUri("Cannot parse URI as UTF-8."))?;
47
17.3k
        Ok(Self::new(utf8_slice))
48
17.3k
    }
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
0
    pub fn get_abs_path(&self) -> &str {
62
0
        const HTTP_SCHEME_PREFIX: &str = "http://";
63
0
64
0
        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
0
            let without_scheme = &self.string[HTTP_SCHEME_PREFIX.len()..];
67
0
            if without_scheme.is_empty() {
68
0
                return "";
69
0
            }
70
0
            // The host in this case includes the port and contains the bytes after http:// up to
71
0
            // the next '/'.
72
0
            match without_scheme.bytes().position(|byte| byte == b'/') {
73
                // Slice access is safe because `position` validates that `len` is a valid index.
74
0
                Some(len) => &without_scheme[len..],
75
0
                None => "",
76
            }
77
        } else {
78
0
            if self.string.starts_with('/') {
79
0
                return self.string.as_str();
80
0
            }
81
0
82
0
            ""
83
        }
84
0
    }
85
}
86
87
/// Wrapper over an HTTP Request Line.
88
0
#[derive(Debug, PartialEq, Eq)]
89
pub struct RequestLine {
90
    method: Method,
91
    uri: Uri,
92
    http_version: Version,
93
}
94
95
impl RequestLine {
96
17.3k
    fn parse_request_line(
97
17.3k
        request_line: &[u8],
98
17.3k
    ) -> std::result::Result<RequestLineParts, RequestError> {
99
17.3k
        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
17.3k
            let method = &request_line[..method_end];
102
103
            // `uri_start` <= `request_line` size.
104
17.3k
            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
17.3k
            let uri_and_version = &request_line[uri_start..];
109
110
17.3k
            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
17.3k
                let uri = &uri_and_version[..uri_end];
113
114
                // `version_start` <= `uri_and_version` size.
115
17.3k
                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
17.3k
                let version = &uri_and_version[version_start..];
119
17.3k
120
17.3k
                return Ok((method, uri, version));
121
0
            }
122
0
        }
123
124
        // Request Line can be valid only if it contains the method, uri and version separated with SP.
125
0
        Err(RequestError::InvalidRequest)
126
17.3k
    }
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
17.3k
    pub fn try_from(request_line: &[u8]) -> Result<Self, RequestError> {
135
17.3k
        let (method, uri, version) = Self::parse_request_line(request_line)?;
136
137
        Ok(Self {
138
17.3k
            method: Method::try_from(method)?,
139
17.3k
            uri: Uri::try_from(uri)?,
140
17.3k
            http_version: Version::try_from(version)?,
141
        })
142
17.3k
    }
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
17.3k
    fn min_len() -> usize {
148
17.3k
        // Addition is safe because these are small constants.
149
17.3k
        Method::Get.raw().len() + 1 + Version::Http10.raw().len() + 2
150
17.3k
    }
151
}
152
153
/// Wrapper over an HTTP Request.
154
0
#[derive(Debug)]
155
pub 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
166
impl 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 micro_http::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
17.3k
    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
17.3k
        if let Some(limit) = max_len {
194
0
            if byte_stream.len() >= limit {
195
0
                return Err(RequestError::InvalidRequest);
196
0
            }
197
17.3k
        }
198
199
        // The first line of the request is the Request Line. The line ending is CR LF.
200
17.3k
        let request_line_end = match find(byte_stream, &[CR, LF]) {
201
17.3k
            Some(len) => len,
202
            // If no CR LF is found in the stream, the request format is invalid.
203
0
            None => return Err(RequestError::InvalidRequest),
204
        };
205
206
        // Slice access is safe because `find` validates that `request_line_end` < `byte_stream` size.
207
17.3k
        let request_line_bytes = &byte_stream[..request_line_end];
208
17.3k
        if request_line_bytes.len() < RequestLine::min_len() {
209
0
            return Err(RequestError::InvalidRequest);
210
17.3k
        }
211
212
17.3k
        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
17.3k
        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
210
            Some(0) => Ok(Self {
220
210
                request_line,
221
210
                headers: Headers::default(),
222
210
                body: None,
223
210
                files: Vec::new(),
224
210
            }),
225
17.1k
            Some(headers_end) => {
226
17.1k
                // Parse the request headers.
227
17.1k
                // Start by removing the leading CR LF from them.
228
17.1k
                // The addition is safe because `find()` guarantees that `request_line_end`
229
17.1k
                // precedes 2 `CRLF` sequences.
230
17.1k
                let headers_start = request_line_end + CRLF_LEN;
231
17.1k
                // Slice access is safe because starting from `request_line_end` there are at least two CRLF
232
17.1k
                // (enforced by `find` at the start of this method).
233
17.1k
                let headers_and_body = &byte_stream[headers_start..];
234
17.1k
                // Because we advanced the start with CRLF_LEN, we now have to subtract CRLF_LEN
235
17.1k
                // from the end in order to keep the same window.
236
17.1k
                // Underflow is not possible here because `byte_stream[request_line_end..]` starts with CR LF,
237
17.1k
                // so `headers_end` can be either zero (this case is treated separately in the first match arm)
238
17.1k
                // or >= 3 (current case).
239
17.1k
                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
17.1k
                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
16.9k
                let body = match headers.content_length() {
247
                    0 => {
248
                        // No request body.
249
76
                        None
250
                    }
251
16.8k
                    content_length => {
252
16.8k
                        if request_line.method == Method::Get {
253
409
                            return Err(RequestError::InvalidRequest);
254
16.4k
                        }
255
16.4k
                        // Multiplication is safe because `CRLF_LEN` is a small constant.
256
16.4k
                        // Addition is also safe because `headers_end` started out as the result
257
16.4k
                        // of `find(<something>, CRLFCRLF)`, then `CRLF_LEN` was subtracted from it.
258
16.4k
                        let crlf_end = headers_end + 2 * CRLF_LEN;
259
16.4k
                        // This can't underflow because `headers_and_body.len()` >= `crlf_end`.
260
16.4k
                        let body_len = headers_and_body.len() - crlf_end;
261
16.4k
                        // Headers suggest we have a body, but the buffer is shorter than the specified
262
16.4k
                        // content length.
263
16.4k
                        if body_len < content_length as usize {
264
722
                            return Err(RequestError::InvalidRequest);
265
15.7k
                        }
266
15.7k
                        // Slice access is safe because `crlf_end` is the index after two CRLF
267
15.7k
                        // (it is <= `headers_and_body` size).
268
15.7k
                        let body_as_bytes = &headers_and_body[crlf_end..];
269
15.7k
                        // If the actual length of the body is different than the `Content-Length` value
270
15.7k
                        // in the headers, then this request is invalid.
271
15.7k
                        if body_as_bytes.len() == content_length as usize {
272
15.6k
                            Some(Body::new(body_as_bytes))
273
                        } else {
274
39
                            return Err(RequestError::InvalidRequest);
275
                        }
276
                    }
277
                };
278
279
15.7k
                Ok(Self {
280
15.7k
                    request_line,
281
15.7k
                    headers,
282
15.7k
                    body,
283
15.7k
                    files: Vec::new(),
284
15.7k
                })
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
31
            None => Err(RequestError::InvalidRequest),
289
        }
290
17.3k
    }
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
0
    pub fn uri(&self) -> &Uri {
296
0
        &self.request_line.uri
297
0
    }
298
299
    /// Returns the HTTP `Version` of the `Request`.
300
0
    pub fn http_version(&self) -> Version {
301
0
        self.request_line.http_version
302
0
    }
303
304
    /// Returns the HTTP `Method` of the `Request`.
305
15.9k
    pub fn method(&self) -> Method {
306
15.9k
        self.request_line.method
307
15.9k
    }
308
}
309
310
#[cfg(test)]
311
mod 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"POST 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
        // Test for invalid uri.
415
        let request_line = b"GET  HTTP/1.0";
416
        assert_eq!(
417
            RequestLine::try_from(request_line).unwrap_err(),
418
            RequestError::InvalidUri("Empty URI not allowed.")
419
        );
420
421
        // Test for invalid HTTP version.
422
        let request_line = b"GET http://localhost/home HTTP/2.0";
423
        assert_eq!(
424
            RequestLine::try_from(request_line).unwrap_err(),
425
            RequestError::InvalidHttpVersion("Unsupported HTTP version.")
426
        );
427
428
        // Test for invalid format with no method, uri or version.
429
        let request_line = b"nothing";
430
        assert_eq!(
431
            RequestLine::try_from(request_line).unwrap_err(),
432
            RequestError::InvalidRequest
433
        );
434
435
        // Test for invalid format with no version.
436
        let request_line = b"GET /";
437
        assert_eq!(
438
            RequestLine::try_from(request_line).unwrap_err(),
439
            RequestError::InvalidRequest
440
        );
441
    }
442
443
    #[test]
444
    fn test_into_request() {
445
        let expected_request = Request {
446
            request_line: RequestLine {
447
                http_version: Version::Http10,
448
                method: Method::Get,
449
                uri: Uri::new("http://localhost/home"),
450
            },
451
            body: None,
452
            files: Vec::new(),
453
            headers: Headers::default(),
454
        };
455
        let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\
456
                                     Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n";
457
        let request = Request::try_from(request_bytes, None).unwrap();
458
        assert_eq!(request, expected_request);
459
        assert_eq!(request.uri(), &Uri::new("http://localhost/home"));
460
        assert_eq!(request.http_version(), Version::Http10);
461
        assert!(request.body.is_none());
462
463
        // Test for invalid Request (missing CR LF).
464
        let request_bytes = b"GET / HTTP/1.1";
465
        assert_eq!(
466
            Request::try_from(request_bytes, None).unwrap_err(),
467
            RequestError::InvalidRequest
468
        );
469
470
        // Test for invalid Request (length is less than minimum).
471
        let request_bytes = b"GET";
472
        assert_eq!(
473
            Request::try_from(request_bytes, None).unwrap_err(),
474
            RequestError::InvalidRequest
475
        );
476
477
        // Test for invalid Request (`GET` requests should have no body).
478
        let request_bytes = b"GET /machine-config HTTP/1.1\r\n\
479
                                        Content-Length: 13\r\n\
480
                                        Content-Type: application/json\r\n\r\nwhatever body";
481
        assert_eq!(
482
            Request::try_from(request_bytes, None).unwrap_err(),
483
            RequestError::InvalidRequest
484
        );
485
486
        // Test for request larger than maximum len provided.
487
        let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\
488
                                     Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n";
489
        assert_eq!(
490
            Request::try_from(request_bytes, Some(20)).unwrap_err(),
491
            RequestError::InvalidRequest
492
        );
493
494
        // Test request smaller than maximum len provided is ok.
495
        let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n\
496
                                     Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n";
497
        assert!(Request::try_from(request_bytes, Some(500)).is_ok());
498
499
        // Test for a request with the headers we are looking for.
500
        let request_bytes = b"PATCH http://localhost/home HTTP/1.1\r\n\
501
                              Expect: 100-continue\r\n\
502
                              Transfer-Encoding: chunked\r\n\
503
                              Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody";
504
        let request = Request::try_from(request_bytes, None).unwrap();
505
        assert_eq!(request.uri(), &Uri::new("http://localhost/home"));
506
        assert_eq!(request.http_version(), Version::Http11);
507
        assert_eq!(request.method(), Method::Patch);
508
        assert!(request.headers.chunked());
509
        assert!(request.headers.expect());
510
        assert_eq!(request.headers.content_length(), 26);
511
        assert_eq!(
512
            request.body.unwrap().body,
513
            String::from("this is not\n\r\na json \nbody")
514
                .as_bytes()
515
                .to_vec()
516
        );
517
518
        // Test for an invalid request format.
519
        Request::try_from(b"PATCH http://localhost/home HTTP/1.1\r\n", None).unwrap_err();
520
521
        // Test for an invalid encoding.
522
        let request_bytes = b"PATCH http://localhost/home HTTP/1.1\r\n\
523
                                Expect: 100-continue\r\n\
524
                                Transfer-Encoding: identity; q=0\r\n\
525
                                Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody";
526
527
        assert!(Request::try_from(request_bytes, None).is_ok());
528
529
        // Test for an invalid content length.
530
        let request_bytes = b"PATCH http://localhost/home HTTP/1.1\r\n\
531
                                Content-Length: 5000\r\n\r\nthis is a short body";
532
        let request = Request::try_from(request_bytes, None).unwrap_err();
533
        assert_eq!(request, RequestError::InvalidRequest);
534
535
        // Test for a request without a body and an optional header.
536
        let request_bytes = b"GET http://localhost/ HTTP/1.0\r\n\
537
                                Accept-Encoding: gzip\r\n\r\n";
538
        let request = Request::try_from(request_bytes, None).unwrap();
539
        assert_eq!(request.uri(), &Uri::new("http://localhost/"));
540
        assert_eq!(request.http_version(), Version::Http10);
541
        assert_eq!(request.method(), Method::Get);
542
        assert!(!request.headers.chunked());
543
        assert!(!request.headers.expect());
544
        assert_eq!(request.headers.content_length(), 0);
545
        assert!(request.body.is_none());
546
547
        let request_bytes = b"GET http://localhost/ HTTP/1.0\r\n\
548
                                Accept-Encoding: identity;q=0\r\n\r\n";
549
        let request = Request::try_from(request_bytes, None);
550
        assert_eq!(
551
            request.unwrap_err(),
552
            RequestError::HeaderError(crate::HttpHeaderError::InvalidValue(
553
                "Accept-Encoding".to_string(),
554
                "identity;q=0".to_string()
555
            ))
556
        );
557
    }
558
}