1use std::collections::HashMap;
10use std::net::SocketAddr;
11use std::ops::{Deref, DerefMut};
12
13use bytes::Bytes;
14use http_body_util::Full;
15
16use crate::context::Context;
17
18pub type Response = hyper::Response<Full<Bytes>>;
19
20pub const MAX_REQUEST_BODY_BYTES: usize = 2 * 1024 * 1024;
29
30pub struct Request {
33 inner: hyper::Request<hyper::body::Incoming>,
34 ctx: Context,
35 peer: Option<SocketAddr>,
36}
37
38impl Request {
39 pub(crate) fn new(
40 inner: hyper::Request<hyper::body::Incoming>,
41 peer: Option<SocketAddr>,
42 ) -> Self {
43 Self {
44 inner,
45 ctx: Context::new(),
46 peer,
47 }
48 }
49
50 pub fn peer_addr(&self) -> Option<SocketAddr> {
59 self.peer
60 }
61
62 pub fn ctx(&self) -> &Context {
64 &self.ctx
65 }
66
67 pub fn ctx_mut(&mut self) -> &mut Context {
69 &mut self.ctx
70 }
71
72 pub fn query(&self) -> FormData {
76 FormData::parse(self.inner.uri().query().unwrap_or(""))
77 }
78
79 pub fn cookie(&self, name: &str) -> Option<String> {
85 let header = self
86 .inner
87 .headers()
88 .get(hyper::header::COOKIE)?
89 .to_str()
90 .ok()?;
91 for pair in header.split(';') {
92 let pair = pair.trim();
93 if let Some((k, v)) = pair.split_once('=') {
94 if k == name {
95 return Some(v.to_string());
96 }
97 }
98 }
99 None
100 }
101
102 pub fn into_parts(self) -> (hyper::http::request::Parts, hyper::body::Incoming, Context) {
105 let (parts, body) = self.inner.into_parts();
106 (parts, body, self.ctx)
107 }
108}
109
110impl Deref for Request {
111 type Target = hyper::Request<hyper::body::Incoming>;
112 fn deref(&self) -> &Self::Target {
113 &self.inner
114 }
115}
116
117impl DerefMut for Request {
118 fn deref_mut(&mut self) -> &mut Self::Target {
119 &mut self.inner
120 }
121}
122
123pub fn text(body: impl Into<String>) -> Response {
125 response(200, "text/plain; charset=utf-8", body.into().into_bytes())
126}
127
128pub fn html(body: impl Into<String>) -> Response {
130 response(200, "text/html; charset=utf-8", body.into().into_bytes())
131}
132
133pub fn json_raw(body: impl Into<String>) -> Response {
138 response(
139 200,
140 "application/json; charset=utf-8",
141 body.into().into_bytes(),
142 )
143}
144
145pub fn status_text(status: u16, body: impl Into<String>) -> Response {
147 response(
148 status,
149 "text/plain; charset=utf-8",
150 body.into().into_bytes(),
151 )
152}
153
154pub fn set_cookie(resp: &mut Response, value: &str) {
161 if let Ok(hv) = value.parse() {
162 resp.headers_mut().append(hyper::header::SET_COOKIE, hv);
163 }
164}
165
166fn response(status: u16, content_type: &'static str, body: Vec<u8>) -> Response {
167 hyper::Response::builder()
168 .status(status)
169 .header("content-type", content_type)
170 .body(Full::new(Bytes::from(body)))
171 .expect("valid response")
172}
173
174pub struct FormData {
186 map: HashMap<String, String>,
187 pairs: Vec<(String, String)>,
188}
189
190impl FormData {
191 pub fn parse(body: &str) -> Self {
193 let mut map = HashMap::new();
194 let mut pairs = Vec::new();
195 for pair in body.split('&') {
196 if pair.is_empty() {
197 continue;
198 }
199 let mut iter = pair.splitn(2, '=');
200 let raw_key = match iter.next() {
201 Some(k) if !k.is_empty() => k,
202 _ => continue,
203 };
204 let raw_val = iter.next().unwrap_or("");
205 let key = percent_decode(raw_key);
206 let val = percent_decode(raw_val);
207 map.insert(key.clone(), val.clone());
208 pairs.push((key, val));
209 }
210 FormData { map, pairs }
211 }
212
213 pub fn get(&self, key: &str) -> Option<&str> {
214 self.map.get(key).map(String::as_str)
215 }
216
217 pub fn get_all(&self, key: &str) -> Vec<&str> {
221 self.pairs
222 .iter()
223 .filter_map(|(k, v)| (k == key).then_some(v.as_str()))
224 .collect()
225 }
226
227 pub fn len(&self) -> usize {
228 self.map.len()
229 }
230
231 pub fn is_empty(&self) -> bool {
232 self.map.is_empty()
233 }
234
235 pub fn into_map(self) -> HashMap<String, String> {
239 self.map
240 }
241}
242
243pub(crate) fn percent_decode(input: &str) -> String {
244 let bytes = input.as_bytes();
245 let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
246 let mut i = 0;
247 while i < bytes.len() {
248 let b = bytes[i];
249 if b == b'+' {
250 out.push(b' ');
251 i += 1;
252 } else if b == b'%' && i + 2 < bytes.len() {
253 if let (Some(h), Some(l)) = (hex_digit(bytes[i + 1]), hex_digit(bytes[i + 2])) {
254 out.push((h << 4) | l);
255 i += 3;
256 continue;
257 }
258 out.push(b);
259 i += 1;
260 } else {
261 out.push(b);
262 i += 1;
263 }
264 }
265 String::from_utf8_lossy(&out).into_owned()
266}
267
268fn hex_digit(b: u8) -> Option<u8> {
269 match b {
270 b'0'..=b'9' => Some(b - b'0'),
271 b'a'..=b'f' => Some(b - b'a' + 10),
272 b'A'..=b'F' => Some(b - b'A' + 10),
273 _ => None,
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn form_parse_decodes_basic_pairs() {
283 let form = FormData::parse("a=1&b=2");
284 assert_eq!(form.get("a"), Some("1"));
285 assert_eq!(form.get("b"), Some("2"));
286 }
287
288 #[test]
289 fn form_parse_decodes_plus_as_space() {
290 let form = FormData::parse("name=John+Doe");
291 assert_eq!(form.get("name"), Some("John Doe"));
292 }
293
294 #[test]
295 fn form_parse_decodes_percent_encoded() {
296 let form = FormData::parse("q=hello%20world%21");
297 assert_eq!(form.get("q"), Some("hello world!"));
298 }
299
300 #[test]
301 fn form_parse_handles_empty_values() {
302 let form = FormData::parse("a=&b=x");
303 assert_eq!(form.get("a"), Some(""));
304 assert_eq!(form.get("b"), Some("x"));
305 }
306
307 #[test]
308 fn form_parse_ignores_empty_pairs() {
309 let form = FormData::parse("&a=1&&b=2&");
310 assert_eq!(form.get("a"), Some("1"));
311 assert_eq!(form.get("b"), Some("2"));
312 assert_eq!(form.len(), 2);
313 }
314
315 #[test]
316 fn form_missing_key_is_none() {
317 let form = FormData::parse("a=1");
318 assert!(form.get("missing").is_none());
319 }
320
321 #[test]
322 fn percent_decode_passes_through_unreserved() {
323 assert_eq!(percent_decode("abcXYZ123-_.~"), "abcXYZ123-_.~");
324 }
325
326 #[test]
327 fn percent_decode_handles_lowercase_and_uppercase_hex() {
328 assert_eq!(percent_decode("%2f%2F"), "//");
329 }
330
331 #[test]
332 fn percent_decode_leaves_invalid_percent_sequences_alone() {
333 assert_eq!(percent_decode("%GG"), "%GG");
334 assert_eq!(percent_decode("end%"), "end%");
335 }
336}