diff options
-rw-r--r-- | Cargo.lock | 135 | ||||
-rw-r--r-- | Cargo.toml | 9 | ||||
-rw-r--r-- | readme.md | 14 | ||||
-rw-r--r-- | src/main.rs | 31 | ||||
-rw-r--r-- | src/util.rs | 134 |
5 files changed, 314 insertions, 9 deletions
diff --git a/Cargo.lock b/Cargo.lock index 1659482..b58a234 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,12 +52,14 @@ dependencies = [ "camino", "confindent", "cutie", + "sha2", "snafu", "time", "tokio", "tokio-util", "tracing", "tracing-subscriber", + "url", ] [[package]] @@ -142,6 +144,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -171,6 +182,25 @@ version = "3.0.0" source = "git+https://github.com/gennyble/confindent#e44cf398923fe90ec07f1ee0cdeae97c85a97d1a" [[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] name = "cutie" version = "0.1.0" dependencies = [ @@ -187,6 +217,16 @@ dependencies = [ ] [[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -247,6 +287,16 @@ dependencies = [ ] [[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -372,6 +422,16 @@ dependencies = [ ] [[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] name = "indexmap" version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -489,6 +549,15 @@ dependencies = [ ] [[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] name = "object" version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -725,6 +794,17 @@ dependencies = [ ] [[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -843,7 +923,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -867,6 +949,21 @@ dependencies = [ ] [[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] name = "tokio" version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1001,18 +1098,56 @@ dependencies = [ ] [[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index c13ed5b..685ff75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,11 @@ confindent = { git = "https://github.com/gennyble/confindent" } cutie = { path = "../cutie" } tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } -time = { version = "0.3.36", features = ["macros", "parsing", "formatting"] } +time = { version = "0.3.36", features = [ + "macros", + "parsing", + "formatting", + "local-offset", +] } +sha2 = "0.10.8" +url = "2.5.0" diff --git a/readme.md b/readme.md index 11fb06d..5bc226f 100644 --- a/readme.md +++ b/readme.md @@ -10,6 +10,20 @@ where I am in the tile. Times are stored by whenwasit in a CSV files. I want to add the times to the bottom of the page again. I miss them. +**Anlytics: For Fun** +Seems fun, anyway, to try and design some weird anyltics system. Can we make it +private enough that it can just be public? + +A friend says that the Analytics should be on a different system so that when it +gets overhwlemed the main site can keep functioning. So, here's an idea: +Just send it a stream of data through UDP? lmao. It can be on a separate VPS +in the same datacenter *(so we can use a private IPv4)* and maybe we have a sort +of heartbeat system to have a reasonable chance of not sending data to a dead +service. + +TODO: +- [] don't log referer if it's ourself + ## Dirfiles These are files that match the name of the directory. 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) + } +} |