diff options
Diffstat (limited to 'src/util.rs')
-rw-r--r-- | src/util.rs | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..11431e5 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,134 @@ +use core::fmt; +use std::ops::Deref; + +use axum::{ + async_trait, + extract::FromRequestParts, + http::{header, request::Parts, HeaderName, StatusCode}, + RequestPartsExt, +}; +use sha2::{Digest, Sha256}; +use time::{format_description::FormatItem, macros::format_description, OffsetDateTime}; + +pub struct RemoteIp(String); + +#[async_trait] +impl<S> FromRequestParts<S> for RemoteIp +where + S: Send + Sync, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> { + 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 + } +} + +const FMT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]"); + +pub struct SessionId([u8; 4]); + +#[async_trait] +impl<S> FromRequestParts<S> for SessionId +where + S: Send + Sync, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> { + let remote = parts + .extract::<RemoteIp>() + .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 = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); + + let string = format!("{remote} - {agent} - {}", date.format(FMT).unwrap()); + + 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 SessionId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in self.0.as_slice() { + write!(f, "{byte:02X}")?; + } + + Ok(()) + } +} + +pub struct Referer(String); + +#[async_trait] +impl<S> FromRequestParts<S> for Referer +where + S: Send + Sync, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, 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(Referer(str.to_owned())), + }, + }, + } + } else { + Err((StatusCode::INTERNAL_SERVER_ERROR, "server error")) + } + } +} + +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) + } +} |