• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Request library for micro HTTP server.
16 //!
17 //! This library implements the basic parts of Request Message from
18 //! (RFC 5322)[ https://www.rfc-editor.org/rfc/rfc5322.html] "HTTP
19 //! Message Format."
20 //!
21 //! This library is only used for serving the netsim client and is not
22 //! meant to implement all aspects of RFC 5322. In particular,
23 //! this library does not implement the following:
24 //! * header field body with multiple lines (section 3.2.2)
25 //! * limits on the lengths of the header section or header field
26 //!
27 //! The main function is `HttpRequest::parse` which can be called
28 //! repeatedly.
29 
30 use std::io::BufRead;
31 use std::io::BufReader;
32 use std::io::Read;
33 
34 pub type StrHeaders<'a> = &'a [(&'a str, &'a str)];
35 
36 #[derive(Debug)]
37 pub struct HttpHeaders {
38     pub headers: Vec<(String, String)>,
39 }
40 
41 impl HttpHeaders {
new() -> Self42     pub fn new() -> Self {
43         Self { headers: Vec::new() }
44     }
45 
46     #[allow(dead_code)]
get(&self, key: &str) -> Option<String>47     pub fn get(&self, key: &str) -> Option<String> {
48         let key = key.to_ascii_lowercase();
49         for (name, value) in self.headers.iter() {
50             if name.to_ascii_lowercase() == key {
51                 return Some(value.to_string());
52             }
53         }
54         None
55     }
56 
57     #[allow(dead_code)]
new_with_headers(str_headers: StrHeaders) -> HttpHeaders58     pub fn new_with_headers(str_headers: StrHeaders) -> HttpHeaders {
59         HttpHeaders {
60             headers: str_headers
61                 .iter()
62                 .map(|(key, value)| -> (String, String) { (key.to_string(), value.to_string()) })
63                 .collect(),
64         }
65     }
66 
iter(&self) -> impl Iterator<Item = &(String, String)>67     pub fn iter(&self) -> impl Iterator<Item = &(String, String)> {
68         self.headers.iter()
69     }
70 
add_header(&mut self, header_key: &str, header_value: &str)71     pub fn add_header(&mut self, header_key: &str, header_value: &str) {
72         self.headers.push((header_key.to_owned(), header_value.to_owned()));
73     }
74 
75     // Same in an impl PartialEq does not work for assert_eq!
76     // so use a method for unit tests
77     #[allow(dead_code)]
eq(&self, other: &[(&str, &str)]) -> bool78     pub fn eq(&self, other: &[(&str, &str)]) -> bool {
79         self.headers.iter().zip(other.iter()).all(|(a, b)| a.0 == b.0 && a.1 == b.1)
80     }
81 }
82 
83 pub struct HttpRequest {
84     pub method: String,
85     pub uri: String,
86     pub version: String,
87     pub headers: HttpHeaders,
88     pub body: Vec<u8>,
89 }
90 
91 impl HttpRequest {
92     // Parse an HTTP request from a BufReader
93 
94     // Clippy does not notice the call to resize, so disable the zero byte vec warning.
95     // https://github.com/rust-lang/rust-clippy/issues/9274
96     #[allow(clippy::read_zero_byte_vec)]
parse<T>(reader: &mut BufReader<T>) -> Result<HttpRequest, String> where T: std::io::Read,97     pub fn parse<T>(reader: &mut BufReader<T>) -> Result<HttpRequest, String>
98     where
99         T: std::io::Read,
100     {
101         let (method, uri, version) = parse_request_line::<T>(reader)?;
102         let headers = parse_header_section::<T>(reader)?;
103         let mut body = Vec::new();
104         if let Some(len) = get_content_length(&headers) {
105             body.resize(len, 0);
106             reader.read_exact(&mut body).map_err(|e| format!("Failed to read body: {e}"))?;
107         }
108         Ok(HttpRequest { method, uri, version, headers, body })
109     }
110 }
111 
112 // Parse the request line of an HTTP request, which contains the method, URI, and version
parse_request_line<T>(reader: &mut BufReader<T>) -> Result<(String, String, String), String> where T: std::io::Read,113 fn parse_request_line<T>(reader: &mut BufReader<T>) -> Result<(String, String, String), String>
114 where
115     T: std::io::Read,
116 {
117     let mut line = String::new();
118     reader.read_line(&mut line).map_err(|e| format!("Failed to read request line: {e}"))?;
119     let mut parts = line.split_whitespace();
120     let method = parts.next().ok_or("Invalid request line, missing method")?;
121     let uri = parts.next().ok_or("Invalid request line, missing uri")?;
122     let version = parts.next().ok_or("Invalid request line, missing version")?;
123     Ok((method.to_string(), uri.to_string(), version.to_string()))
124 }
125 
126 // Parse the Headers Section from (RFC 5322)[https://www.rfc-editor.org/rfc/rfc5322.html]
127 // "HTTP Message Format."
parse_header_section<T>(reader: &mut BufReader<T>) -> Result<HttpHeaders, String> where T: std::io::Read,128 fn parse_header_section<T>(reader: &mut BufReader<T>) -> Result<HttpHeaders, String>
129 where
130     T: std::io::Read,
131 {
132     let mut headers = HttpHeaders::new();
133     for line in reader.lines() {
134         let line = line.map_err(|e| format!("Failed to parse headers: {e}"))?;
135         if let Some((name, value)) = line.split_once(':') {
136             headers.add_header(name, value.trim());
137         } else if line.len() > 1 {
138             // no colon in a header line
139             return Err(format!("Invalid header line: {line}"));
140         } else {
141             // empty line marks the end of headers
142             break;
143         }
144     }
145     Ok(headers)
146 }
147 
get_content_length(headers: &HttpHeaders) -> Option<usize>148 fn get_content_length(headers: &HttpHeaders) -> Option<usize> {
149     if let Some(value) = headers.get("Content-Length") {
150         match value.parse::<usize>() {
151             Ok(n) => return Some(n),
152             Err(_) => return None,
153         }
154     }
155     None
156 }
157 
158 #[cfg(test)]
159 mod tests {
160     use super::*;
161 
162     #[test]
test_parse()163     fn test_parse() {
164         let request = concat!(
165             "GET /index.html HTTP/1.1\r\n",
166             "Host: example.com\r\nContent-Length: 13\r\n\r\n",
167             "Hello World\r\n"
168         );
169         let mut reader = BufReader::new(request.as_bytes());
170         let http_request = HttpRequest::parse::<&[u8]>(&mut reader).unwrap();
171         assert_eq!(http_request.method, "GET");
172         assert_eq!(http_request.uri, "/index.html");
173         assert_eq!(http_request.version, "HTTP/1.1");
174         assert!(http_request.headers.eq(&[("Host", "example.com"), ("Content-Length", "13")]));
175         assert_eq!(http_request.body, b"Hello World\r\n".to_vec());
176     }
177 
178     #[test]
test_parse_without_body()179     fn test_parse_without_body() {
180         let request = concat!("GET /index.html HTTP/1.1\r\n", "Host: example.com\r\n\r\n");
181         let mut reader = BufReader::new(request.as_bytes());
182         let http_request = HttpRequest::parse::<&[u8]>(&mut reader).unwrap();
183         assert_eq!(http_request.method, "GET");
184         assert_eq!(http_request.uri, "/index.html");
185         assert_eq!(http_request.version, "HTTP/1.1");
186         assert!(http_request.headers.eq(&[("Host", "example.com")]));
187         assert_eq!(http_request.body, Vec::<u8>::new());
188     }
189 
190     #[test]
test_parse_without_content_length()191     fn test_parse_without_content_length() {
192         let request =
193             concat!("GET /index.html HTTP/1.1\r\n", "Host: example.com\r\n\r\n", "Hello World\r\n");
194         let mut reader = BufReader::new(request.as_bytes());
195         let http_request = HttpRequest::parse::<&[u8]>(&mut reader).unwrap();
196         assert_eq!(http_request.method, "GET");
197         assert_eq!(http_request.uri, "/index.html");
198         assert_eq!(http_request.version, "HTTP/1.1");
199         assert!(http_request.headers.eq(&[("Host", "example.com")]));
200         assert_eq!(http_request.body, b"");
201     }
202 }
203