1use std::collections::HashMap;
10use std::ops::{Deref, DerefMut};
11
12use bytes::Bytes;
13use http_body_util::Full;
14
15use crate::context::Context;
16
17pub type Response = hyper::Response<Full<Bytes>>;
18
19pub struct Request {
21 inner: hyper::Request<hyper::body::Incoming>,
22 ctx: Context,
23}
24
25impl Request {
26 pub(crate) fn new(inner: hyper::Request<hyper::body::Incoming>) -> Self {
27 Self {
28 inner,
29 ctx: Context::new(),
30 }
31 }
32
33 pub fn ctx(&self) -> &Context {
35 &self.ctx
36 }
37
38 pub fn ctx_mut(&mut self) -> &mut Context {
40 &mut self.ctx
41 }
42
43 pub fn query(&self) -> FormData {
47 FormData::parse(self.inner.uri().query().unwrap_or(""))
48 }
49
50 pub fn into_parts(self) -> (hyper::http::request::Parts, hyper::body::Incoming, Context) {
53 let (parts, body) = self.inner.into_parts();
54 (parts, body, self.ctx)
55 }
56}
57
58impl Deref for Request {
59 type Target = hyper::Request<hyper::body::Incoming>;
60 fn deref(&self) -> &Self::Target {
61 &self.inner
62 }
63}
64
65impl DerefMut for Request {
66 fn deref_mut(&mut self) -> &mut Self::Target {
67 &mut self.inner
68 }
69}
70
71pub fn text(body: impl Into<String>) -> Response {
73 response(200, "text/plain; charset=utf-8", body.into().into_bytes())
74}
75
76pub fn html(body: impl Into<String>) -> Response {
78 response(200, "text/html; charset=utf-8", body.into().into_bytes())
79}
80
81pub fn json_raw(body: impl Into<String>) -> Response {
86 response(
87 200,
88 "application/json; charset=utf-8",
89 body.into().into_bytes(),
90 )
91}
92
93pub fn status_text(status: u16, body: impl Into<String>) -> Response {
95 response(
96 status,
97 "text/plain; charset=utf-8",
98 body.into().into_bytes(),
99 )
100}
101
102fn response(status: u16, content_type: &'static str, body: Vec<u8>) -> Response {
103 hyper::Response::builder()
104 .status(status)
105 .header("content-type", content_type)
106 .body(Full::new(Bytes::from(body)))
107 .expect("valid response")
108}
109
110pub struct FormData {
115 map: HashMap<String, String>,
116}
117
118impl FormData {
119 pub fn parse(body: &str) -> Self {
121 let mut map = HashMap::new();
122 for pair in body.split('&') {
123 if pair.is_empty() {
124 continue;
125 }
126 let mut iter = pair.splitn(2, '=');
127 let raw_key = match iter.next() {
128 Some(k) if !k.is_empty() => k,
129 _ => continue,
130 };
131 let raw_val = iter.next().unwrap_or("");
132 map.insert(percent_decode(raw_key), percent_decode(raw_val));
133 }
134 FormData { map }
135 }
136
137 pub fn get(&self, key: &str) -> Option<&str> {
138 self.map.get(key).map(String::as_str)
139 }
140
141 pub fn len(&self) -> usize {
142 self.map.len()
143 }
144
145 pub fn is_empty(&self) -> bool {
146 self.map.is_empty()
147 }
148}
149
150pub(crate) fn percent_decode(input: &str) -> String {
151 let bytes = input.as_bytes();
152 let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
153 let mut i = 0;
154 while i < bytes.len() {
155 let b = bytes[i];
156 if b == b'+' {
157 out.push(b' ');
158 i += 1;
159 } else if b == b'%' && i + 2 < bytes.len() {
160 if let (Some(h), Some(l)) = (hex_digit(bytes[i + 1]), hex_digit(bytes[i + 2])) {
161 out.push((h << 4) | l);
162 i += 3;
163 continue;
164 }
165 out.push(b);
166 i += 1;
167 } else {
168 out.push(b);
169 i += 1;
170 }
171 }
172 String::from_utf8_lossy(&out).into_owned()
173}
174
175fn hex_digit(b: u8) -> Option<u8> {
176 match b {
177 b'0'..=b'9' => Some(b - b'0'),
178 b'a'..=b'f' => Some(b - b'a' + 10),
179 b'A'..=b'F' => Some(b - b'A' + 10),
180 _ => None,
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn form_parse_decodes_basic_pairs() {
190 let form = FormData::parse("a=1&b=2");
191 assert_eq!(form.get("a"), Some("1"));
192 assert_eq!(form.get("b"), Some("2"));
193 }
194
195 #[test]
196 fn form_parse_decodes_plus_as_space() {
197 let form = FormData::parse("name=John+Doe");
198 assert_eq!(form.get("name"), Some("John Doe"));
199 }
200
201 #[test]
202 fn form_parse_decodes_percent_encoded() {
203 let form = FormData::parse("q=hello%20world%21");
204 assert_eq!(form.get("q"), Some("hello world!"));
205 }
206
207 #[test]
208 fn form_parse_handles_empty_values() {
209 let form = FormData::parse("a=&b=x");
210 assert_eq!(form.get("a"), Some(""));
211 assert_eq!(form.get("b"), Some("x"));
212 }
213
214 #[test]
215 fn form_parse_ignores_empty_pairs() {
216 let form = FormData::parse("&a=1&&b=2&");
217 assert_eq!(form.get("a"), Some("1"));
218 assert_eq!(form.get("b"), Some("2"));
219 assert_eq!(form.len(), 2);
220 }
221
222 #[test]
223 fn form_missing_key_is_none() {
224 let form = FormData::parse("a=1");
225 assert!(form.get("missing").is_none());
226 }
227
228 #[test]
229 fn percent_decode_passes_through_unreserved() {
230 assert_eq!(percent_decode("abcXYZ123-_.~"), "abcXYZ123-_.~");
231 }
232
233 #[test]
234 fn percent_decode_handles_lowercase_and_uppercase_hex() {
235 assert_eq!(percent_decode("%2f%2F"), "//");
236 }
237
238 #[test]
239 fn percent_decode_leaves_invalid_percent_sequences_alone() {
240 assert_eq!(percent_decode("%GG"), "%GG");
241 assert_eq!(percent_decode("end%"), "end%");
242 }
243}