//! Provides the [`Parts`] and [`Body`] convenience wrappers. use cookie::Cookie; use header::CONTENT_TYPE; use mime::{APPLICATION_WWW_FORM_URLENCODED, Mime}; use serde::{Deserialize, de::DeserializeOwned}; use hyper::{body::Bytes, header}; use hyper::http::request::Parts as ReqParts; use std::collections::HashMap; use crate::security; use error::*; type HyperRequest = hyper::Request; /// Convenience wrapper around [`hyper::Body`]. pub struct Body { body: hyper::Body, content_type: Option, } /// Convert [`hyper::Request`] to ([`Parts`], [`Body`]) pub fn adapt<'a>(req: HyperRequest) -> (Parts, Body) { let (parts, body) = req.into_parts(); let body = Body{body, content_type: parts.headers.get(CONTENT_TYPE).map(|x| x.to_owned())}; let parts = Parts{parts, cookies: None}; (parts, body) } /// Convenience wrapper around [`hyper::http::request::Parts`]. pub struct Parts { parts: ReqParts, cookies: Option>>, } #[derive(Deserialize)] struct CsrfData { csrf: String, } impl Parts { pub fn cookies(&mut self) -> &HashMap { if let Some(ref cookies) = self.cookies { return cookies } let mut cookies = HashMap::new(); for header in self.parts.headers.get_all(header::COOKIE) { let raw_str = match std::str::from_utf8(header.as_bytes()) { Ok(string) => string, Err(_) => continue }; for cookie_str in raw_str.split(';').map(|s| s.trim()) { if let Ok(cookie) = Cookie::parse_encoded(cookie_str) { cookies.insert(cookie.name().to_string(), cookie.into_owned()); } } } self.cookies = Some(cookies); &self.cookies.as_ref().unwrap() } pub fn method(&self) -> &hyper::Method { &self.parts.method } pub fn headers(&self) -> &hyper::HeaderMap { &self.parts.headers } pub fn uri(&self) -> &hyper::Uri { &self.parts.uri } /// Parses the query string of the request into a given struct. pub fn query(&self) -> Result { serde_urlencoded::from_str::(self.parts.uri.query().unwrap_or("")).map_err(QueryError) } } impl Body { pub async fn into_bytes(self) -> Result { hyper::body::to_bytes(self.body).await.map_err(BodyError) } /// Parses a `application/x-www-form-urlencoded` request body into a given struct. /// /// This does make you vulnerable to CSRF, so you normally want to use /// [`Body::into_form_csrf()`] instead. /// /// # Example /// /// ``` /// use hyper::{Response}; /// use sputnik::{request::Body, Error}; /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Message {text: String, year: i64} /// /// async fn greet(body: Body) -> Result, Error> { /// let msg: Message = body.into_form().await?; /// Ok(Response::new(format!("hello {}", msg.text).into())) /// } /// ``` pub async fn into_form(self) -> Result { self.enforce_content_type(APPLICATION_WWW_FORM_URLENCODED)?; let full_body = self.into_bytes().await?; serde_urlencoded::from_bytes::(&full_body).map_err(FormError::Deserialize) } /// Parses a `application/x-www-form-urlencoded` request body into a given struct. /// Protects from CSRF by checking that the request body contains the same token retrieved from the cookies. /// /// The CSRF parameter is expected as the `csrf` parameter in the request body. /// This means for HTML forms you need to embed the token as a hidden input. /// /// # Example /// /// ``` /// use hyper::{Method}; /// use sputnik::{request::{Parts, Body}, response::Response, Error}; /// use sputnik::security::CsrfToken; /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Message {text: String} /// /// async fn greet(req: &mut Parts, body: Body) -> Result { /// let mut response = Response::new(); /// let csrf_token = CsrfToken::from_parts(req, &mut response); /// *response.body() = match (req.method()) { /// &Method::GET => format!("
/// {}
", csrf_token.html_input()).into(), /// &Method::POST => { /// let msg: Message = body.into_form_csrf(&csrf_token).await?; /// format!("hello {}", msg.text).into() /// }, /// _ => return Err(Error::method_not_allowed("only GET and POST allowed".to_owned())), /// }; /// Ok(response) /// } /// ``` pub async fn into_form_csrf(self, csrf_token: &security::CsrfToken) -> Result { self.enforce_content_type(APPLICATION_WWW_FORM_URLENCODED)?; let full_body = self.into_bytes().await?; let csrf_data = serde_urlencoded::from_bytes::(&full_body).map_err(|_| CsrfProtectedFormError::NoCsrf)?; csrf_token.matches(csrf_data.csrf)?; serde_urlencoded::from_bytes::(&full_body).map_err(CsrfProtectedFormError::Deserialize) } fn enforce_content_type(&self, mime: Mime) -> Result<(), WrongContentTypeError> { if let Some(content_type) = &self.content_type { if *content_type == mime.to_string() { return Ok(()) } } Err(WrongContentTypeError{expected: mime, received: self.content_type.as_ref().and_then(|h| h.to_str().ok().map(|s| s.to_owned()))}) } } pub mod error { use mime::Mime; use thiserror::Error; use hyper::StatusCode; use crate::security::CsrfError; #[derive(Error, Debug)] #[error("query deserialize error: {0}")] pub struct QueryError(pub serde_urlencoded::de::Error); impl_into_error_simple!(QueryError, StatusCode::BAD_REQUEST); #[derive(Error, Debug)] #[error("failed to read body")] pub struct BodyError(pub hyper::Error); impl_into_error_simple!(BodyError, StatusCode::BAD_REQUEST); #[derive(Error, Debug)] #[error("expected Content-Type {expected} but received {}", received.as_ref().unwrap_or(&"nothing".to_owned()))] pub struct WrongContentTypeError { pub expected: Mime, pub received: Option, } #[derive(Error, Debug)] pub enum FormError { #[error("{0}")] ContentType(#[from] WrongContentTypeError), #[error("{0}")] Body(#[from] BodyError), #[error("form deserialize error: {0}")] Deserialize(#[from] serde_urlencoded::de::Error), } impl_into_error_simple!(FormError, StatusCode::BAD_REQUEST); #[derive(Error, Debug)] pub enum CsrfProtectedFormError { #[error("{0}")] ContentType(#[from] WrongContentTypeError), #[error("{0}")] Body(#[from] BodyError), #[error("form deserialize error: {0}")] Deserialize(#[from] serde_urlencoded::de::Error), #[error("no csrf token in form data")] NoCsrf, #[error("{0}")] Csrf(#[from] CsrfError), } impl_into_error_simple!(CsrfProtectedFormError, StatusCode::BAD_REQUEST); }