use std::ops; #[cfg(feature = "scgi_srv")] use std::{ collections::HashMap, convert::TryFrom, }; use anyhow::*; use percent_encoding::percent_decode_str; use uriparse::URIReference; use crate::types::Certificate; #[cfg(feature="user_management")] use serde::{Serialize, de::DeserializeOwned}; #[cfg(feature="user_management")] use crate::user_management::{UserManager, User}; pub struct Request { uri: URIReference<'static>, input: Option, certificate: Option, trailing_segments: Option>, #[cfg(feature="user_management")] manager: UserManager, #[cfg(feature = "scgi_srv")] headers: HashMap, } impl Request { pub fn new( #[cfg(feature = "gemini_srv")] mut uri: URIReference<'static>, #[cfg(feature = "scgi_srv")] headers: HashMap, #[cfg(feature="user_management")] manager: UserManager, ) -> Result { #[cfg(feature = "scgi_srv")] let (mut uri, certificate) = ( URIReference::try_from( format!( "{}{}", headers.get("PATH_INFO") .context("PATH_INFO header not received from SCGI client")? .as_str(), headers.get("QUERY_STRING") .map(|q| format!("?{}", q)) .unwrap_or_else(String::new), ).as_str() ) .context("Request URI is invalid")? .into_owned(), headers.get("TLS_CLIENT_HASH").cloned(), ); uri.normalize(); let input = match uri.query() { None => None, Some(query) => { let input = percent_decode_str(query.as_str()) .decode_utf8() .context("Request URI query contains invalid UTF-8")? .into_owned(); Some(input) } }; Ok(Self { uri, input, #[cfg(feature = "scgi_srv")] certificate, #[cfg(feature = "gemini_srv")] certificate: None, trailing_segments: None, #[cfg(feature = "scgi_srv")] headers, #[cfg(feature="user_management")] manager, }) } pub const fn uri(&self) -> &URIReference { &self.uri } #[allow(clippy::missing_const_for_fn)] /// All of the path segments following the route to which this request was bound. /// /// For example, if this handler was bound to the `/api` route, and a request was /// received to `/api/v1/endpoint`, then this value would be `["v1", "endpoint"]`. /// This should not be confused with [`path_segments()`](Self::path_segments()), which /// contains *all* of the segments, not just those trailing the route. /// /// If the trailing segments have not been set, this method will panic, but this /// should only be possible if you are constructing the Request yourself. Requests /// to handlers registered through [`add_route()`](crate::Server::add_route()) will /// always have trailing segments set. pub fn trailing_segments(&self) -> &Vec { self.trailing_segments.as_ref().unwrap() } /// All of the segments in this path, percent decoded /// /// For example, for a request to `/api/v1/endpoint`, this would return `["api", "v1", /// "endpoint"]`, no matter what route the handler that received this request was /// bound to. This is not to be confused with /// [`trailing_segments()`](Self::trailing_segments), which contains only the segments /// following the bound route. /// /// Additionally, unlike `trailing_segments()`, this method percent decodes the path. pub fn path_segments(&self) -> Vec { self.uri() .path() .segments() .iter() .map(|segment| percent_decode_str(segment.as_str()).decode_utf8_lossy().into_owned()) .collect::>() } pub fn input(&self) -> Option<&str> { self.input.as_deref() } #[cfg(feature="scgi_srv")] /// View any headers sent by the SCGI client /// /// When an SCGI client delivers a request (e.g. when your gemini server sends a /// request to this app), it includes many headers which aren't always included in /// the request otherwise. Bear in mind that **not all SCGI clients send the same /// headers**, and these are *never* available when operating in `gemini_srv` mode. /// /// Some examples of headers mollybrown sets are: /// - `REMOTE_ADDR` (The user's IP address and port) /// - `TLS_CLIENT_SUBJECT_CN` (The CommonName on the user's certificate, when present) /// - `SERVER_NAME` (The host name of the server the request was received on) /// - `SERVER_SOFTWARE` (= "MOLLY_BROWN") /// - `SCRIPT_PATH` (The prefix the script is being served on) pub const fn headers(&self) -> &HashMap { &self.headers } pub fn set_cert(&mut self, cert: Option) { self.certificate = cert; } pub fn set_trailing(&mut self, segments: Vec) { self.trailing_segments = Some(segments); } #[allow(clippy::missing_const_for_fn)] pub fn certificate(&self) -> Option<&Certificate> { self.certificate.as_ref() } #[cfg(feature="user_management")] /// Attempt to determine the user who sent this request /// /// May return a variant depending on if the client used a client certificate, and if /// they've registered as a user yet. pub fn user(&self) -> Result> where UserData: Serialize + DeserializeOwned { Ok(self.manager.get_user(self.certificate())?) } #[cfg(feature="user_management")] /// Expose the server's UserManager /// /// Can be used to query users, or directly access the database pub fn user_manager(&self) -> &UserManager { &self.manager } } impl ops::Deref for Request { type Target = URIReference<'static>; fn deref(&self) -> &Self::Target { &self.uri } }