diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 31 | ||||
-rw-r--r-- | src/util.rs | 134 |
2 files changed, 157 insertions, 8 deletions
diff --git a/src/main.rs b/src/main.rs index b43e463..57c373e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod markup; mod settings; mod templated; mod timeparse; +mod util; use std::{os::unix::fs::MetadataExt, str::FromStr}; @@ -25,6 +26,7 @@ use fs::Filesystem; use settings::Settings; use tokio_util::io::ReaderStream; use tracing_subscriber::{prelude::*, EnvFilter}; +use util::{Referer, RemoteIp, SessionId}; use crate::{ fs::{PathResolution, Webpath}, @@ -35,11 +37,8 @@ use crate::{ async fn main() { match std::env::args().nth(1).as_deref() { Some("atomizer") => atomizer::main(), - Some("serve") => /* fallthrough*/ - { - () - } + Some("serve") => (), _ => (), } @@ -74,16 +73,23 @@ async fn main() { axum::serve(listener, app).await.unwrap() } -async fn index_handler(fse: Extension<Filesystem>, se: Extension<Settings>) -> Response { - handler(fse, se, Path(String::from("/"))).await +async fn index_handler( + fse: Extension<Filesystem>, + se: Extension<Settings>, + sid: SessionId, + rfr: Option<Referer>, +) -> Response { + handler(fse, se, sid, rfr, Path(String::from("/"))).await } async fn handler( Extension(fs): Extension<Filesystem>, Extension(settings): Extension<Settings>, + sid: SessionId, + rfr: Option<Referer>, Path(path): Path<String>, ) -> Response { - match falible_handler(fs, settings, path).await { + match falible_handler(fs, settings, sid, rfr, path).await { Ok(resp) => resp, Err(re) => Response::builder() .body(Body::from(re.to_string())) @@ -94,6 +100,8 @@ async fn handler( async fn falible_handler( fs: Filesystem, settings: Settings, + sid: SessionId, + rfr: Option<Referer>, path: String, ) -> Result<Response, RuntimeError> { tracing::debug!("webpath = {path}"); @@ -105,7 +113,14 @@ async fn falible_handler( return Ok(redirect(webpath.as_dir())); } - tracing::info!("serving {webpath}"); + match rfr { + None => { + tracing::info!("[{sid}] serving {webpath}"); + } + Some(referer) => { + tracing::info!("[{sid}] (refer {referer}) serving {webpath}"); + } + } let ext = resolve.filepath.extension().unwrap_or_default(); 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) + } +} |