use std::{ fmt, ops::Deref, sync::atomic::{AtomicU8, AtomicUsize}, }; use axum::{ RequestPartsExt, extract::{FromRequestParts, OptionalFromRequestParts}, http::{HeaderName, StatusCode, header, request::Parts}, }; use sha2::{Digest, Sha256}; static REQUEST_ID: AtomicU8 = AtomicU8::new(0); #[derive(Debug, Clone)] pub struct RequestContext { id: u8, hash: SessionHash, referer: Option, } impl RequestContext { pub fn hash(&self) -> &SessionHash { &self.hash } pub fn referer(&self) -> Option<&str> { self.referer.as_deref() } pub fn identifying_string(&self) -> String { format!("[{}|{:>3}]", self.hash, self.id) } } impl FromRequestParts for RequestContext where S: Send + Sync, { type Rejection = (StatusCode, &'static str); async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { let id = REQUEST_ID.fetch_add(1, std::sync::atomic::Ordering::Acquire); let hash = parts.extract::().await?; let referer = parts.extract::>().await?; Ok(Self { id, hash, referer }) } } impl fmt::Display for RequestContext { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let id = self.id; match self.referer() { None => write!(f, "[{}|{id:>3}]", self.hash), Some(refer) => write!(f, "[{}|{id:>3}] (refer {refer})", self.hash), } } } #[derive(Debug, Clone)] pub struct RemoteIp(String); impl FromRequestParts for RemoteIp where S: Send + Sync, { type Rejection = (StatusCode, &'static str); async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { if let Some(forwarded_for) = parts .headers .get(HeaderName::from_static("x-forwarded-for")) { match forwarded_for.to_str() { Err(_e) => Err((StatusCode::INTERNAL_SERVER_ERROR, "server error")), Ok(remoteip) => Ok(Self(remoteip.to_owned())), } } else { Err((StatusCode::INTERNAL_SERVER_ERROR, "server error")) } } } impl Deref for RemoteIp { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Debug, Copy, Clone)] pub struct SessionHash([u8; 4]); impl FromRequestParts for SessionHash where S: Send + Sync, { type Rejection = (StatusCode, &'static str); async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { let remote = parts .extract::() .await .map(|rip| rip.0) .unwrap_or(String::from("127.0.0.1")); let agent = parts .headers .get(header::USER_AGENT) .and_then(|ua| ua.to_str().ok()) .unwrap_or("unknown"); let date = timeparse::whichever_now(); let date_str = timeparse::format::iso_date(&date.date()); let string = format!("{remote} - {agent} - {date_str}"); let mut hasher = Sha256::new(); hasher.update(string.as_bytes()); let hash = hasher.finalize(); let slc: [u8; 4] = hash.as_slice()[0..4].try_into().unwrap(); Ok(Self(slc)) } } impl fmt::Display for SessionHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for byte in self.0.as_slice() { write!(f, "{byte:02X}")?; } Ok(()) } } #[derive(Debug, Clone)] pub struct Referer(String); impl OptionalFromRequestParts for Referer where S: Send + Sync, { type Rejection = (StatusCode, &'static str); async fn from_request_parts( parts: &mut Parts, _state: &S, ) -> Result, Self::Rejection> { if let Some(refer) = parts.headers.get(header::REFERER) { match refer.to_str() { Err(_e) => Err((StatusCode::INTERNAL_SERVER_ERROR, "server error")), Ok(refer) => match url::Url::parse(refer) { Err(_e) => Err(( StatusCode::INTERNAL_SERVER_ERROR, "failed to parse referer url", )), Ok(url) => match url.host_str() { None => Err((StatusCode::INTERNAL_SERVER_ERROR, "referer url had no host")), Some(str) => Ok(Some(Referer(str.to_owned()))), }, }, } } else { Ok(None) } } } impl Deref for Referer { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } impl fmt::Display for Referer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } }