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 cookie(&self, name: &str) -> Option<String> {
56 let header = self
57 .inner
58 .headers()
59 .get(hyper::header::COOKIE)?
60 .to_str()
61 .ok()?;
62 for pair in header.split(';') {
63 let pair = pair.trim();
64 if let Some((k, v)) = pair.split_once('=') {
65 if k == name {
66 return Some(v.to_string());
67 }
68 }
69 }
70 None
71 }
72
73 pub fn into_parts(self) -> (hyper::http::request::Parts, hyper::body::Incoming, Context) {
76 let (parts, body) = self.inner.into_parts();
77 (parts, body, self.ctx)
78 }
79}
80
81impl Deref for Request {
82 type Target = hyper::Request<hyper::body::Incoming>;
83 fn deref(&self) -> &Self::Target {
84 &self.inner
85 }
86}
87
88impl DerefMut for Request {
89 fn deref_mut(&mut self) -> &mut Self::Target {
90 &mut self.inner
91 }
92}
93
94pub fn text(body: impl Into<String>) -> Response {
96 response(200, "text/plain; charset=utf-8", body.into().into_bytes())
97}
98
99pub fn html(body: impl Into<String>) -> Response {
101 response(200, "text/html; charset=utf-8", body.into().into_bytes())
102}
103
104pub fn json_raw(body: impl Into<String>) -> Response {
109 response(
110 200,
111 "application/json; charset=utf-8",
112 body.into().into_bytes(),
113 )
114}
115
116pub fn status_text(status: u16, body: impl Into<String>) -> Response {
118 response(
119 status,
120 "text/plain; charset=utf-8",
121 body.into().into_bytes(),
122 )
123}
124
125pub fn set_cookie(resp: &mut Response, value: &str) {
132 if let Ok(hv) = value.parse() {
133 resp.headers_mut().append(hyper::header::SET_COOKIE, hv);
134 }
135}
136
137fn response(status: u16, content_type: &'static str, body: Vec<u8>) -> Response {
138 hyper::Response::builder()
139 .status(status)
140 .header("content-type", content_type)
141 .body(Full::new(Bytes::from(body)))
142 .expect("valid response")
143}
144
145pub struct FormData {
150 map: HashMap<String, String>,
151}
152
153impl FormData {
154 pub fn parse(body: &str) -> Self {
156 let mut map = HashMap::new();
157 for pair in body.split('&') {
158 if pair.is_empty() {
159 continue;
160 }
161 let mut iter = pair.splitn(2, '=');
162 let raw_key = match iter.next() {
163 Some(k) if !k.is_empty() => k,
164 _ => continue,
165 };
166 let raw_val = iter.next().unwrap_or("");
167 map.insert(percent_decode(raw_key), percent_decode(raw_val));
168 }
169 FormData { map }
170 }
171
172 pub fn get(&self, key: &str) -> Option<&str> {
173 self.map.get(key).map(String::as_str)
174 }
175
176 pub fn len(&self) -> usize {
177 self.map.len()
178 }
179
180 pub fn is_empty(&self) -> bool {
181 self.map.is_empty()
182 }
183}
184
185pub(crate) fn percent_decode(input: &str) -> String {
186 let bytes = input.as_bytes();
187 let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
188 let mut i = 0;
189 while i < bytes.len() {
190 let b = bytes[i];
191 if b == b'+' {
192 out.push(b' ');
193 i += 1;
194 } else if b == b'%' && i + 2 < bytes.len() {
195 if let (Some(h), Some(l)) = (hex_digit(bytes[i + 1]), hex_digit(bytes[i + 2])) {
196 out.push((h << 4) | l);
197 i += 3;
198 continue;
199 }
200 out.push(b);
201 i += 1;
202 } else {
203 out.push(b);
204 i += 1;
205 }
206 }
207 String::from_utf8_lossy(&out).into_owned()
208}
209
210fn hex_digit(b: u8) -> Option<u8> {
211 match b {
212 b'0'..=b'9' => Some(b - b'0'),
213 b'a'..=b'f' => Some(b - b'a' + 10),
214 b'A'..=b'F' => Some(b - b'A' + 10),
215 _ => None,
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn form_parse_decodes_basic_pairs() {
225 let form = FormData::parse("a=1&b=2");
226 assert_eq!(form.get("a"), Some("1"));
227 assert_eq!(form.get("b"), Some("2"));
228 }
229
230 #[test]
231 fn form_parse_decodes_plus_as_space() {
232 let form = FormData::parse("name=John+Doe");
233 assert_eq!(form.get("name"), Some("John Doe"));
234 }
235
236 #[test]
237 fn form_parse_decodes_percent_encoded() {
238 let form = FormData::parse("q=hello%20world%21");
239 assert_eq!(form.get("q"), Some("hello world!"));
240 }
241
242 #[test]
243 fn form_parse_handles_empty_values() {
244 let form = FormData::parse("a=&b=x");
245 assert_eq!(form.get("a"), Some(""));
246 assert_eq!(form.get("b"), Some("x"));
247 }
248
249 #[test]
250 fn form_parse_ignores_empty_pairs() {
251 let form = FormData::parse("&a=1&&b=2&");
252 assert_eq!(form.get("a"), Some("1"));
253 assert_eq!(form.get("b"), Some("2"));
254 assert_eq!(form.len(), 2);
255 }
256
257 #[test]
258 fn form_missing_key_is_none() {
259 let form = FormData::parse("a=1");
260 assert!(form.get("missing").is_none());
261 }
262
263 #[test]
264 fn percent_decode_passes_through_unreserved() {
265 assert_eq!(percent_decode("abcXYZ123-_.~"), "abcXYZ123-_.~");
266 }
267
268 #[test]
269 fn percent_decode_handles_lowercase_and_uppercase_hex() {
270 assert_eq!(percent_decode("%2f%2F"), "//");
271 }
272
273 #[test]
274 fn percent_decode_leaves_invalid_percent_sequences_alone() {
275 assert_eq!(percent_decode("%GG"), "%GG");
276 assert_eq!(percent_decode("end%"), "end%");
277 }
278}