1use origin_domain::{AppError, Result};
9
10const MAX_HEADER_BYTES: usize = 64 * 1024;
12
13const MAX_BODY_BYTES: usize = 1024 * 1024;
15
16const HEADER_TERMINATOR: &[u8] = b"\r\n\r\n";
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct HttpRequest {
20 pub method: String,
21 pub path: String,
22 pub headers: Vec<(String, String)>,
24 pub body: Vec<u8>,
25}
26
27impl HttpRequest {
28 pub fn header(&self, name: &str) -> Option<&str> {
29 let name = name.to_ascii_lowercase();
30 self.headers
31 .iter()
32 .find(|(key, _)| *key == name)
33 .map(|(_, value)| value.as_str())
34 }
35
36 pub fn bearer_token(&self) -> Option<&str> {
38 self.header("authorization")?
39 .strip_prefix("Bearer ")
40 .map(str::trim)
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct HttpResponse {
46 pub status: u16,
47 pub body: Vec<u8>,
48}
49
50impl HttpResponse {
51 pub fn json(status: u16, value: &serde_json::Value) -> Self {
52 let body = serde_json::to_vec(value).unwrap_or_else(|_| b"null".to_vec());
53 Self { status, body }
54 }
55
56 pub fn text(status: u16, message: impl Into<String>) -> Self {
57 Self {
58 status,
59 body: message.into().into_bytes(),
60 }
61 }
62
63 fn reason(&self) -> &'static str {
64 match self.status {
65 200 => "OK",
66 400 => "Bad Request",
67 401 => "Unauthorized",
68 404 => "Not Found",
69 405 => "Method Not Allowed",
70 413 => "Payload Too Large",
71 500 => "Internal Server Error",
72 _ => "Error",
73 }
74 }
75
76 pub fn encode(&self) -> Vec<u8> {
78 let head = format!(
79 "HTTP/1.1 {} {}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
80 self.status,
81 self.reason(),
82 self.body.len()
83 );
84 let mut out = head.into_bytes();
85 out.extend_from_slice(&self.body);
86 out
87 }
88}
89
90pub fn parse_request(buffer: &[u8]) -> Result<Option<(HttpRequest, usize)>> {
95 let Some(header_end) = find_terminator(buffer) else {
96 if buffer.len() > MAX_HEADER_BYTES {
97 return Err(AppError::validation("http headers exceed the size limit"));
98 }
99 return Ok(None);
100 };
101
102 let head = std::str::from_utf8(&buffer[..header_end])
103 .map_err(|_| AppError::validation("http headers are not utf-8"))?;
104
105 let mut lines = head.split("\r\n");
106 let request_line = lines
107 .next()
108 .ok_or_else(|| AppError::validation("empty http request"))?;
109
110 let mut parts = request_line.split_whitespace();
111 let method = parts
112 .next()
113 .ok_or_else(|| AppError::validation("missing http method"))?
114 .to_owned();
115 let path = parts
116 .next()
117 .ok_or_else(|| AppError::validation("missing http path"))?
118 .to_owned();
119
120 let mut headers = Vec::new();
121 for line in lines {
122 if line.is_empty() {
123 continue;
124 }
125 let (key, value) = line
126 .split_once(':')
127 .ok_or_else(|| AppError::validation("malformed http header"))?;
128 headers.push((key.trim().to_ascii_lowercase(), value.trim().to_owned()));
129 }
130
131 let content_length = headers
132 .iter()
133 .find(|(key, _)| key == "content-length")
134 .map(|(_, value)| {
135 value
136 .parse::<usize>()
137 .map_err(|_| AppError::validation("invalid content-length"))
138 })
139 .transpose()?
140 .unwrap_or(0);
141
142 if content_length > MAX_BODY_BYTES {
143 return Err(AppError::validation("http body exceeds the size limit"));
144 }
145
146 let body_start = header_end + HEADER_TERMINATOR.len();
147 let body_end = body_start + content_length;
148 if buffer.len() < body_end {
149 return Ok(None);
150 }
151
152 Ok(Some((
153 HttpRequest {
154 method,
155 path,
156 headers,
157 body: buffer[body_start..body_end].to_vec(),
158 },
159 body_end,
160 )))
161}
162
163fn find_terminator(buffer: &[u8]) -> Option<usize> {
164 buffer
165 .windows(HEADER_TERMINATOR.len())
166 .position(|window| window == HEADER_TERMINATOR)
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn a_complete_post_is_parsed() {
175 let mut raw = Vec::new();
176 raw.extend_from_slice(b"POST /mcp HTTP/1.1");
177 raw.extend_from_slice(b"\r\n");
178 raw.extend_from_slice(b"Host: 127.0.0.1");
179 raw.extend_from_slice(b"\r\n");
180 raw.extend_from_slice(b"Authorization: Bearer abc");
181 raw.extend_from_slice(b"\r\n");
182 raw.extend_from_slice(b"Content-Length: 7");
183 raw.extend_from_slice(b"\r\n\r\n");
184 raw.extend_from_slice(b"{\"a\":1}");
185
186 let (request, consumed) = parse_request(&raw).unwrap().expect("complete request");
187
188 assert_eq!(request.method, "POST");
189 assert_eq!(request.path, "/mcp");
190 assert_eq!(request.body, b"{\"a\":1}");
191 assert_eq!(request.bearer_token(), Some("abc"));
192 assert_eq!(consumed, raw.len());
193 }
194
195 #[test]
196 fn an_incomplete_request_asks_for_more() {
197 let raw = b"POST /mcp HTTP/1.1\r\nContent-Length: 20\r\n\r\n{\"a\"";
198 assert!(parse_request(raw).unwrap().is_none());
199 }
200
201 #[test]
202 fn a_request_without_a_body_parses() {
203 let raw = b"GET /mcp HTTP/1.1\r\nHost: x\r\n\r\n";
204 let (request, _) = parse_request(raw).unwrap().expect("complete request");
205 assert_eq!(request.method, "GET");
206 assert!(request.body.is_empty());
207 }
208
209 #[test]
210 fn a_missing_bearer_prefix_yields_no_token() {
211 let raw = b"POST /mcp HTTP/1.1\r\nAuthorization: Basic xyz\r\n\r\n";
212 let (request, _) = parse_request(raw).unwrap().unwrap();
213 assert_eq!(request.bearer_token(), None);
214 }
215
216 #[test]
217 fn a_response_encodes_a_content_length_and_closes() {
218 let response = HttpResponse::text(401, "nope");
219 let encoded = String::from_utf8(response.encode()).unwrap();
220
221 assert!(encoded.starts_with("HTTP/1.1 401 Unauthorized\r\n"));
222 assert!(encoded.contains("Content-Length: 4\r\n"));
223 assert!(encoded.ends_with("\r\n\r\nnope"));
224 }
225
226 #[test]
227 fn an_oversized_body_is_rejected() {
228 let raw = format!(
229 "POST /mcp HTTP/1.1\r\nContent-Length: {}\r\n\r\n",
230 MAX_BODY_BYTES + 1
231 );
232 assert!(parse_request(raw.as_bytes()).is_err());
233 }
234}