use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use bytes::Bytes;
use http_body_util::Full;
use crate::context::Context;
pub type Response = hyper::Response<Full<Bytes>>;
pub struct Request {
inner: hyper::Request<hyper::body::Incoming>,
ctx: Context,
}
impl Request {
pub(crate) fn new(inner: hyper::Request<hyper::body::Incoming>) -> Self {
Self {
inner,
ctx: Context::new(),
}
}
pub fn ctx(&self) -> &Context {
&self.ctx
}
pub fn ctx_mut(&mut self) -> &mut Context {
&mut self.ctx
}
pub fn query(&self) -> FormData {
FormData::parse(self.inner.uri().query().unwrap_or(""))
}
pub fn into_parts(self) -> (hyper::http::request::Parts, hyper::body::Incoming, Context) {
let (parts, body) = self.inner.into_parts();
(parts, body, self.ctx)
}
}
impl Deref for Request {
type Target = hyper::Request<hyper::body::Incoming>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for Request {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
pub fn text(body: impl Into<String>) -> Response {
response(200, "text/plain; charset=utf-8", body.into().into_bytes())
}
pub fn html(body: impl Into<String>) -> Response {
response(200, "text/html; charset=utf-8", body.into().into_bytes())
}
pub fn json_raw(body: impl Into<String>) -> Response {
response(
200,
"application/json; charset=utf-8",
body.into().into_bytes(),
)
}
pub fn status_text(status: u16, body: impl Into<String>) -> Response {
response(
status,
"text/plain; charset=utf-8",
body.into().into_bytes(),
)
}
fn response(status: u16, content_type: &'static str, body: Vec<u8>) -> Response {
hyper::Response::builder()
.status(status)
.header("content-type", content_type)
.body(Full::new(Bytes::from(body)))
.expect("valid response")
}
pub struct FormData {
map: HashMap<String, String>,
}
impl FormData {
pub fn parse(body: &str) -> Self {
let mut map = HashMap::new();
for pair in body.split('&') {
if pair.is_empty() {
continue;
}
let mut iter = pair.splitn(2, '=');
let raw_key = match iter.next() {
Some(k) if !k.is_empty() => k,
_ => continue,
};
let raw_val = iter.next().unwrap_or("");
map.insert(percent_decode(raw_key), percent_decode(raw_val));
}
FormData { map }
}
pub fn get(&self, key: &str) -> Option<&str> {
self.map.get(key).map(String::as_str)
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
}
pub(crate) fn percent_decode(input: &str) -> String {
let bytes = input.as_bytes();
let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == b'+' {
out.push(b' ');
i += 1;
} else if b == b'%' && i + 2 < bytes.len() {
if let (Some(h), Some(l)) = (hex_digit(bytes[i + 1]), hex_digit(bytes[i + 2])) {
out.push((h << 4) | l);
i += 3;
continue;
}
out.push(b);
i += 1;
} else {
out.push(b);
i += 1;
}
}
String::from_utf8_lossy(&out).into_owned()
}
fn hex_digit(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn form_parse_decodes_basic_pairs() {
let form = FormData::parse("a=1&b=2");
assert_eq!(form.get("a"), Some("1"));
assert_eq!(form.get("b"), Some("2"));
}
#[test]
fn form_parse_decodes_plus_as_space() {
let form = FormData::parse("name=John+Doe");
assert_eq!(form.get("name"), Some("John Doe"));
}
#[test]
fn form_parse_decodes_percent_encoded() {
let form = FormData::parse("q=hello%20world%21");
assert_eq!(form.get("q"), Some("hello world!"));
}
#[test]
fn form_parse_handles_empty_values() {
let form = FormData::parse("a=&b=x");
assert_eq!(form.get("a"), Some(""));
assert_eq!(form.get("b"), Some("x"));
}
#[test]
fn form_parse_ignores_empty_pairs() {
let form = FormData::parse("&a=1&&b=2&");
assert_eq!(form.get("a"), Some("1"));
assert_eq!(form.get("b"), Some("2"));
assert_eq!(form.len(), 2);
}
#[test]
fn form_missing_key_is_none() {
let form = FormData::parse("a=1");
assert!(form.get("missing").is_none());
}
#[test]
fn percent_decode_passes_through_unreserved() {
assert_eq!(percent_decode("abcXYZ123-_.~"), "abcXYZ123-_.~");
}
#[test]
fn percent_decode_handles_lowercase_and_uppercase_hex() {
assert_eq!(percent_decode("%2f%2F"), "//");
}
#[test]
fn percent_decode_leaves_invalid_percent_sequences_alone() {
assert_eq!(percent_decode("%GG"), "%GG");
assert_eq!(percent_decode("end%"), "end%");
}
}