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