diff --git a/Cargo.lock b/Cargo.lock index f005ec6..67b57a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -459,8 +459,8 @@ dependencies = [ [[package]] name = "const-macros" -version = "0.1.1" -source = "git+https://git.javalsai.tuxcord.net/rust/const-macros.git#6a37f26eaa6349c18ac7c74b1d3938d287013942" +version = "0.1.2" +source = "git+https://git.javalsai.tuxcord.net/rust/const-macros.git#6f91e1e1a6ce2e5cdb5866a1e1d7841ed5e3cd6f" dependencies = [ "const-macros-proc", ] @@ -468,7 +468,7 @@ dependencies = [ [[package]] name = "const-macros-proc" version = "0.1.0" -source = "git+https://git.javalsai.tuxcord.net/rust/const-macros.git#6a37f26eaa6349c18ac7c74b1d3938d287013942" +source = "git+https://git.javalsai.tuxcord.net/rust/const-macros.git#6f91e1e1a6ce2e5cdb5866a1e1d7841ed5e3cd6f" dependencies = [ "magic", "sha2", @@ -1200,6 +1200,7 @@ dependencies = [ "moka", "pamsock", "serde", + "sha2", "thiserror", "tokio", "toml", diff --git a/Cargo.toml b/Cargo.toml index 136fe08..87c4cf7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,13 +25,14 @@ actix-web = "4.13" anstyle = "1.0" anyhow = "1.0" clap = { version = "4.5", features = ["derive"] } -const-macros = { git = "https://git.javalsai.tuxcord.net/rust/const-macros.git", version = "0.1.1" } +const-macros = { git = "https://git.javalsai.tuxcord.net/rust/const-macros.git", version = "0.1.2" } const-str = { version = "1.1", features = ["proc"] } futures-util = "0.3" libc = "0.2" magic = "0.16" moka = { version = "0.12", features = ["async-lock", "future"] } serde = { version = "1.0", features = ["derive"] } +sha2 = "0.10" thiserror = "2.0" tokio = { version = "1.49", features = ["full"] } toml = "1.0" diff --git a/src/server/caches/mod.rs b/src/server/caches/mod.rs index 2985803..dd43183 100644 --- a/src/server/caches/mod.rs +++ b/src/server/caches/mod.rs @@ -1,7 +1,8 @@ use std::{ffi::OsStr, io, os::unix::fs::MetadataExt, path::Path, sync::Arc}; -use ::users::os::unix::UserExt; +use ::users::os::unix::UserExt as _; use moka::future::{Cache, CacheBuilder}; +use sha2::Digest as _; use tokio::{fs::File, io::AsyncReadExt}; use crate::{ @@ -14,12 +15,13 @@ pub mod users; const MIME_IMAGE_PREFIX: &str = "image/"; -pub struct MimeWithBytes { +pub struct ImageInfo { pub mime: Box, pub bytes: Box<[u8]>, + pub shasum: Box, } -pub type Image = Arc; +pub type Image = Arc; // TODO: most of the cache methods here block the executor, if we wanna commit to async we'd have // to consider that @@ -115,15 +117,23 @@ impl<'a> AppCache<'a> { for subpath in consts::USER_PFP_PATHS { let path = home.join(subpath); + // I'm relying too much on this condition if let Ok(img_buf) = read_limited_path::<{ consts::MAX_PFP_SIZE }>(&path).await && let Ok(Ok(mime)) = self.magic_mime_cookie.buffer(&img_buf) // TODO: first layer // error is actually // relevant && mime.starts_with(MIME_IMAGE_PREFIX) { - return Some(Arc::new(MimeWithBytes { + let shasum = sha2::Sha256::digest(&img_buf); + let shasum = format!( + "\"{}\"", + crate::utils::shasum::sha256sum_to_hex_string(&shasum) + ); + + return Some(Arc::new(ImageInfo { mime: mime.into_boxed_str(), bytes: img_buf.into_boxed_slice(), + shasum: shasum.into_boxed_str(), })); } } diff --git a/src/server/services/images.rs b/src/server/services/images.rs index 7d620a1..8a62fa3 100644 --- a/src/server/services/images.rs +++ b/src/server/services/images.rs @@ -5,8 +5,6 @@ //! //! Must be scoped at [`ws::IMAGES`] -// TODO: etags - use actix_web::{ HttpResponse, get, http::header, @@ -32,6 +30,10 @@ async fn get_default_image() -> HttpResponse { header::CACHE_CONTROL, consts::DEFAULT_USER_PFP_CACHES_HEADER, )) + .insert_header(( + header::ETAG, + const_str::concat!('"', consts::DEFAULT_USER_PFP.shasum_str, '"'), + )) .content_type(consts::DEFAULT_USER_PFP.mime) .body(web::Bytes::from_static(consts::DEFAULT_USER_PFP.bytes)) } @@ -53,6 +55,7 @@ async fn get_image( web::Either::Right( HttpResponse::Ok() .insert_header((header::CACHE_CONTROL, consts::USER_CACHES_HEADER)) + .insert_header((header::ETAG, img.shasum.as_ref())) .content_type(img.mime.as_ref()) .body(web::Bytes::copy_from_slice(img.bytes.as_ref())), )