diff --git a/Cargo.lock b/Cargo.lock index c4189be..220c77b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -986,6 +986,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "harsh" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6fce2283849822530a18d7d8eeb1719ac65a27cfb6649c0dc8dfd2d2cc5edfb" + [[package]] name = "hashbrown" version = "0.12.3" @@ -1290,6 +1296,7 @@ dependencies = [ "clap", "env_logger", "futures", + "harsh", "lazy_static", "linkify", "log", diff --git a/Cargo.toml b/Cargo.toml index 8adc0ce..54e79a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ syntect = "5.0" qrcode-generator = "4.1.6" rust-embed = "6.4.2" mime_guess = "2.0.4" +harsh = "0.2" [profile.release] lto = true diff --git a/src/args.rs b/src/args.rs index 3c61cbd..1dc3e72 100644 --- a/src/args.rs +++ b/src/args.rs @@ -89,6 +89,9 @@ pub struct Args { #[clap(long, env = "MICROBIN_CUSTOM_CSS")] pub custom_css: Option, + + #[clap(long, env = "MICROBIN_HASH_IDS")] + pub hash_ids: bool, } #[derive(Debug, Clone)] diff --git a/src/endpoints/create.rs b/src/endpoints/create.rs index 30f463e..591d304 100644 --- a/src/endpoints/create.rs +++ b/src/endpoints/create.rs @@ -1,6 +1,7 @@ use crate::dbio::save_to_file; use crate::pasta::PastaFile; use crate::util::animalnumbers::to_animal_names; +use crate::util::hashids::to_hashids; use crate::util::misc::is_valid_url; use crate::{AppState, Pasta, ARGS}; use actix_multipart::Multipart; @@ -188,10 +189,12 @@ pub async fn create( save_to_file(&pastas); + let slug = if ARGS.hash_ids { + to_hashids(id) + } else { + to_animal_names(id) + }; Ok(HttpResponse::Found() - .append_header(( - "Location", - format!("{}/pasta/{}", ARGS.public_path, to_animal_names(id)), - )) + .append_header(("Location", format!("{}/pasta/{}", ARGS.public_path, slug))) .finish()) } diff --git a/src/endpoints/edit.rs b/src/endpoints/edit.rs index 54ef1f6..b361aec 100644 --- a/src/endpoints/edit.rs +++ b/src/endpoints/edit.rs @@ -2,6 +2,7 @@ use crate::args::Args; use crate::dbio::save_to_file; use crate::endpoints::errors::ErrorTemplate; use crate::util::animalnumbers::to_u64; +use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::remove_expired; use crate::{AppState, Pasta, ARGS}; use actix_multipart::Multipart; @@ -20,7 +21,11 @@ struct EditTemplate<'a> { pub async fn get_edit(data: web::Data, id: web::Path) -> HttpResponse { let mut pastas = data.pastas.lock().unwrap(); - let id = to_u64(&*id.into_inner()).unwrap_or(0); + let id = if ARGS.hash_ids { + hashid_to_u64(&*id).unwrap_or(0) + } else { + to_u64(&*id.into_inner()).unwrap_or(0) + }; remove_expired(&mut pastas); @@ -59,7 +64,11 @@ pub async fn post_edit( .finish()); } - let id = to_u64(&*id.into_inner()).unwrap_or(0); + let id = if ARGS.hash_ids { + hashid_to_u64(&*id).unwrap_or(0) + } else { + to_u64(&*id.into_inner()).unwrap_or(0) + }; let mut pastas = data.pastas.lock().unwrap(); @@ -85,7 +94,10 @@ pub async fn post_edit( save_to_file(&pastas); return Ok(HttpResponse::Found() - .append_header(("Location", format!("{}/pasta/{}", ARGS.public_path, pastas[i].id_as_animals()))) + .append_header(( + "Location", + format!("{}/pasta/{}", ARGS.public_path, pastas[i].id_as_animals()), + )) .finish()); } else { break; diff --git a/src/endpoints/pasta.rs b/src/endpoints/pasta.rs index 4242ab7..208664c 100644 --- a/src/endpoints/pasta.rs +++ b/src/endpoints/pasta.rs @@ -3,6 +3,7 @@ use crate::dbio::save_to_file; use crate::endpoints::errors::ErrorTemplate; use crate::pasta::Pasta; use crate::util::animalnumbers::to_u64; +use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::remove_expired; use crate::AppState; use actix_web::rt::time; @@ -22,8 +23,11 @@ pub async fn getpasta(data: web::Data, id: web::Path) -> HttpR // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - // get the u64 id from the animal names in the path - let id = to_u64(&*id.into_inner()).unwrap_or(0); + let id = if ARGS.hash_ids { + hashid_to_u64(&*id).unwrap_or(0) + } else { + to_u64(&*id.into_inner()).unwrap_or(0) + }; // remove expired pastas (including this one if needed) remove_expired(&mut pastas); @@ -83,8 +87,11 @@ pub async fn redirecturl(data: web::Data, id: web::Path) -> Ht // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - // get the u64 id from the animal names in the path - let id = to_u64(&*id.into_inner()).unwrap_or(0); + let id = if ARGS.hash_ids { + hashid_to_u64(&*id).unwrap_or(0) + } else { + to_u64(&*id.into_inner()).unwrap_or(0) + }; // remove expired pastas (including this one if needed) remove_expired(&mut pastas); @@ -92,6 +99,7 @@ pub async fn redirecturl(data: web::Data, id: web::Path) -> Ht // find the index of the pasta in the collection based on u64 id let mut index: usize = 0; let mut found: bool = false; + for (i, pasta) in pastas.iter().enumerate() { if pasta.id == id { index = i; @@ -146,8 +154,11 @@ pub async fn getrawpasta(data: web::Data, id: web::Path) -> St // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - // get the u64 id from the animal names in the path - let id = to_u64(&*id.into_inner()).unwrap_or(0); + let id = if ARGS.hash_ids { + hashid_to_u64(&*id).unwrap_or(0) + } else { + to_u64(&*id.into_inner()).unwrap_or(0) + }; // remove expired pastas (including this one if needed) remove_expired(&mut pastas); diff --git a/src/endpoints/remove.rs b/src/endpoints/remove.rs index 6d552b0..0378a00 100644 --- a/src/endpoints/remove.rs +++ b/src/endpoints/remove.rs @@ -4,6 +4,7 @@ use crate::args::ARGS; use crate::endpoints::errors::ErrorTemplate; use crate::pasta::PastaFile; use crate::util::animalnumbers::to_u64; +use crate::util::hashids::to_u64 as hashid_to_u64; use crate::util::misc::remove_expired; use crate::AppState; use askama::Template; @@ -19,20 +20,30 @@ pub async fn remove(data: web::Data, id: web::Path) -> HttpRes let mut pastas = data.pastas.lock().unwrap(); - let id = to_u64(&*id.into_inner()).unwrap_or(0); + let id = if ARGS.hash_ids { + hashid_to_u64(&*id).unwrap_or(0) + } else { + to_u64(&*id.into_inner()).unwrap_or(0) + }; for (i, pasta) in pastas.iter().enumerate() { if pasta.id == id { // remove the file itself if let Some(PastaFile { name, .. }) = &pasta.file { - if fs::remove_file(format!("./pasta_data/public/{}/{}", pasta.id_as_animals(), name)) - .is_err() + if fs::remove_file(format!( + "./pasta_data/public/{}/{}", + pasta.id_as_animals(), + name + )) + .is_err() { log::error!("Failed to delete file {}!", name) } // and remove the containing directory - if fs::remove_dir(format!("./pasta_data/public/{}/", pasta.id_as_animals())).is_err() { + if fs::remove_dir(format!("./pasta_data/public/{}/", pasta.id_as_animals())) + .is_err() + { log::error!("Failed to delete directory {}!", name) } } diff --git a/src/main.rs b/src/main.rs index 86e9322..cfbcc72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ pub mod util { pub mod animalnumbers; pub mod auth; pub mod dbio; + pub mod hashids; pub mod misc; pub mod syntaxhighlighter; } diff --git a/src/pasta.rs b/src/pasta.rs index b6e98bd..f20ce7d 100644 --- a/src/pasta.rs +++ b/src/pasta.rs @@ -5,7 +5,9 @@ use std::fmt; use std::path::Path; use std::time::{SystemTime, UNIX_EPOCH}; +use crate::args::ARGS; use crate::util::animalnumbers::to_animal_names; +use crate::util::hashids::to_hashids; use crate::util::syntaxhighlighter::html_highlight; #[derive(Serialize, Deserialize, PartialEq, Eq)] @@ -48,7 +50,11 @@ pub struct Pasta { impl Pasta { pub fn id_as_animals(&self) -> String { - to_animal_names(self.id) + if ARGS.hash_ids { + to_hashids(self.id) + } else { + to_animal_names(self.id) + } } pub fn created_as_string(&self) -> String { diff --git a/src/util/hashids.rs b/src/util/hashids.rs new file mode 100644 index 0000000..e9cdb1f --- /dev/null +++ b/src/util/hashids.rs @@ -0,0 +1,18 @@ +use harsh::Harsh; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref HARSH: Harsh = Harsh::builder().length(6).build().unwrap(); +} + +pub fn to_hashids(number: u64) -> String { + HARSH.encode(&[number]) +} + +pub fn to_u64(hash_id: &str) -> Result { + let ids = HARSH + .decode(hash_id) + .map_err(|_e| "Failed to decode hash ID")?; + let id = ids.get(0).ok_or("No ID found in hash ID")?; + Ok(*id) +}