diff --git a/.env b/.env index 59cfac1f..b9df4cad 100644 --- a/.env +++ b/.env @@ -151,6 +151,10 @@ export MICROBIN_GC_DAYS=90 # Default value: false export MICROBIN_ENABLE_BURN_AFTER=true +# Enables or disables the "Custom Url" function +# Default value: false +export MICROBIN_ENABLE_CUSTOM_URL=true + # Sets the default burn after setting on the main screen. # Default value: 0. Available expiration options: 1, 10, # 100, 1000, 10000, 0 (= no limit) diff --git a/README.md b/README.md index f3a7c6dc..df83ca1b 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ On our website [microbin.eu](https://microbin.eu) you will find the following: - QR code support - URL shortening and redirection - Animal names instead of random numbers for upload identifiers (64 animals) +- Custom URL - SQLite and JSON database support - Private and public, editable and uneditable, automatically and never expiring uploads - Automatic dark mode and custom styling support with very little CSS and only vanilla JS (see [`water.css`](https://github.com/kognise/water.css)) diff --git a/compose.yaml b/compose.yaml index 60ffb6ab..aaeaf54f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -31,6 +31,7 @@ services: MICROBIN_THREADS: ${MICROBIN_THREADS} MICROBIN_GC_DAYS: ${MICROBIN_GC_DAYS} MICROBIN_ENABLE_BURN_AFTER: ${MICROBIN_ENABLE_BURN_AFTER} + MICROBIN_ENABLE_CUSTOM_URL: ${MICROBIN_ENABLE_CUSTOM_URL} MICROBIN_DEFAULT_BURN_AFTER: ${MICROBIN_DEFAULT_BURN_AFTER} MICROBIN_WIDE: ${MICROBIN_WIDE} MICROBIN_QR: ${MICROBIN_QR} diff --git a/src/args.rs b/src/args.rs index 64dccb54..5d1bd58e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -103,6 +103,9 @@ pub struct Args { #[clap(long, env = "MICROBIN_ENABLE_READONLY")] pub enable_readonly: bool, + #[clap(long, env = "MICROBIN_ENABLE_CUSTOM_URL")] + pub enable_custom_url: bool, + #[clap(long, env = "MICROBIN_DEFAULT_EXPIRY", default_value = "24hour")] pub default_expiry: String, @@ -114,7 +117,7 @@ pub struct Args { #[clap(long, env = "MICROBIN_CUSTOM_CSS")] pub custom_css: Option, - + #[clap(long, env = "MICROBIN_HASH_IDS")] pub hash_ids: bool, @@ -211,6 +214,7 @@ impl Args { max_file_size_encrypted_mb: self.max_file_size_encrypted_mb, max_file_size_unencrypted_mb: self.max_file_size_unencrypted_mb, disable_update_checking: self.disable_update_checking, + enable_custom_url: self.enable_custom_url, } } } diff --git a/src/endpoints/auth_upload.rs b/src/endpoints/auth_upload.rs index fd605db1..4efc8a8f 100644 --- a/src/endpoints/auth_upload.rs +++ b/src/endpoints/auth_upload.rs @@ -1,7 +1,6 @@ use crate::args::{Args, ARGS}; use crate::endpoints::errors::ErrorTemplate; -use crate::util::animalnumbers::to_u64; -use crate::util::hashids::to_u64 as hashid_to_u64; +use crate::util::hashids::alias_comparator; use crate::util::misc::remove_expired; use crate::AppState; use actix_web::{get, web, HttpResponse}; @@ -25,14 +24,10 @@ pub async fn auth_upload(data: web::Data, id: web::Path) -> Ht remove_expired(&mut pastas); - let intern_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); for (_i, pasta) in pastas.iter().enumerate() { - if pasta.id == intern_id { + if comparator(pasta) { return HttpResponse::Ok().content_type("text/html").body( AuthPasta { args: &ARGS, @@ -65,14 +60,10 @@ pub async fn auth_upload_with_status( let (id, status) = param.into_inner(); - let intern_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); for (_i, pasta) in pastas.iter().enumerate() { - if pasta.id == intern_id { + if comparator(pasta) { return HttpResponse::Ok().content_type("text/html").body( AuthPasta { args: &ARGS, @@ -100,14 +91,10 @@ pub async fn auth_raw_pasta(data: web::Data, id: web::Path) -> remove_expired(&mut pastas); - let intern_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); for (_i, pasta) in pastas.iter().enumerate() { - if pasta.id == intern_id { + if comparator(pasta) { return HttpResponse::Ok().content_type("text/html").body( AuthPasta { args: &ARGS, @@ -140,14 +127,10 @@ pub async fn auth_raw_pasta_with_status( let (id, status) = param.into_inner(); - let intern_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); for (_i, pasta) in pastas.iter().enumerate() { - if pasta.id == intern_id { + if comparator(pasta) { return HttpResponse::Ok().content_type("text/html").body( AuthPasta { args: &ARGS, @@ -175,14 +158,10 @@ pub async fn auth_edit_private(data: web::Data, id: web::Path) remove_expired(&mut pastas); - let intern_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); for (_, pasta) in pastas.iter().enumerate() { - if pasta.id == intern_id { + if comparator(pasta) { return HttpResponse::Ok().content_type("text/html").body( AuthPasta { args: &ARGS, @@ -215,14 +194,10 @@ pub async fn auth_edit_private_with_status( let (id, status) = param.into_inner(); - let intern_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); for (_i, pasta) in pastas.iter().enumerate() { - if pasta.id == intern_id { + if comparator(pasta) { return HttpResponse::Ok().content_type("text/html").body( AuthPasta { args: &ARGS, @@ -250,14 +225,10 @@ pub async fn auth_file(data: web::Data, id: web::Path) -> Http remove_expired(&mut pastas); - let intern_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); for (_, pasta) in pastas.iter().enumerate() { - if pasta.id == intern_id { + if comparator(pasta) { return HttpResponse::Ok().content_type("text/html").body( AuthPasta { args: &ARGS, @@ -290,14 +261,10 @@ pub async fn auth_file_with_status( let (id, status) = param.into_inner(); - let intern_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); for (_i, pasta) in pastas.iter().enumerate() { - if pasta.id == intern_id { + if comparator(pasta) { return HttpResponse::Ok().content_type("text/html").body( AuthPasta { args: &ARGS, @@ -325,14 +292,10 @@ pub async fn auth_remove_private(data: web::Data, id: web::Path() as u64, content: String::from(""), file: None, + custom_alias: None, extension: String::from(""), private: false, readonly: false, @@ -207,6 +211,31 @@ pub async fn create( } continue; } + "custom_alias" => { + if !ARGS.enable_custom_url { + continue; + } + while let Some(chunk) = field.try_next().await? { + let custom_alias_unchecked = + std::str::from_utf8(&chunk).unwrap().trim().to_string(); + // todo check it + if hashid_to_u64(&custom_alias_unchecked).is_ok() + || to_u64(&&custom_alias_unchecked).is_ok() + { + // prevent conflicts with default url. + return Err(ErrorForbidden("Conflicts with default URL format")); + } + if pastas + .iter() + .any(|pasta| pasta.custom_alias.as_ref() == Some(&custom_alias_unchecked)) + { + // prevent conflicts with default url. + return Err(ErrorForbidden("Custom url conflicts with existing pasta")); + } + new_pasta.custom_alias = Some(custom_alias_unchecked); + } + continue; + } "file" => { if ARGS.no_file_upload { continue; @@ -303,6 +332,7 @@ pub async fn create( } let encrypt_server = new_pasta.encrypt_server; + let alias = new_pasta.custom_alias.clone(); pastas.push(new_pasta); @@ -312,7 +342,9 @@ pub async fn create( } } - let slug = if ARGS.hash_ids { + let slug = if let Some(alias) = alias { + alias + } else if ARGS.hash_ids { to_hashids(id) } else { to_animal_names(id) diff --git a/src/endpoints/edit.rs b/src/endpoints/edit.rs index e379b788..b64a7cf7 100644 --- a/src/endpoints/edit.rs +++ b/src/endpoints/edit.rs @@ -1,8 +1,7 @@ use crate::args::Args; use crate::endpoints::errors::ErrorTemplate; -use crate::util::animalnumbers::to_u64; use crate::util::db::update; -use crate::util::hashids::to_u64 as hashid_to_u64; +use crate::util::hashids::alias_comparator; use crate::util::misc::{decrypt, encrypt, remove_expired}; use crate::{AppState, Pasta, ARGS}; use actix_multipart::Multipart; @@ -23,16 +22,12 @@ struct EditTemplate<'a> { pub async fn get_edit(data: web::Data, id: web::Path) -> HttpResponse { let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); remove_expired(&mut pastas); for pasta in pastas.iter() { - if pasta.id == id { + if comparator(pasta) { if !pasta.editable { return HttpResponse::Found() .append_header(("Location", format!("{}/", ARGS.public_path_as_str()))) @@ -75,16 +70,12 @@ pub async fn get_edit_with_status( let (id, status) = param.into_inner(); - let intern_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); remove_expired(&mut pastas); for pasta in pastas.iter() { - if pasta.id == intern_id { + if comparator(pasta) { if !pasta.editable { return HttpResponse::Found() .append_header(("Location", format!("{}/", ARGS.public_path_as_str()))) @@ -127,11 +118,7 @@ pub async fn post_edit_private( // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); let mut password = String::from(""); @@ -150,7 +137,7 @@ pub async fn post_edit_private( let mut index: usize = 0; let mut found: bool = false; for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { + if comparator(pasta) { index = i; found = true; break; @@ -214,11 +201,7 @@ pub async fn post_submit_edit_private( // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); let mut password = String::from(""); let mut new_content = String::from(""); @@ -243,7 +226,7 @@ pub async fn post_submit_edit_private( let mut index: usize = 0; let mut found: bool = false; for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { + if comparator(pasta) { index = i; found = true; break; @@ -304,11 +287,7 @@ pub async fn post_edit( id: web::Path, mut payload: Multipart, ) -> Result { - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); let mut pastas = data.pastas.lock().unwrap(); @@ -331,7 +310,7 @@ pub async fn post_edit( } for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { + if comparator(pasta) { if pasta.editable && !pasta.encrypt_client { if pastas[i].readonly || pastas[i].encrypt_server { if password != *"" { diff --git a/src/endpoints/file.rs b/src/endpoints/file.rs index 289bf663..ad58e455 100644 --- a/src/endpoints/file.rs +++ b/src/endpoints/file.rs @@ -2,9 +2,9 @@ use std::fs::{self, File}; use std::path::PathBuf; use crate::args::ARGS; -use crate::util::hashids::to_u64 as hashid_to_u64; +use crate::util::hashids::alias_comparator; use crate::util::misc::remove_expired; -use crate::util::{animalnumbers::to_u64, misc::decrypt_file}; +use crate::util::misc::decrypt_file; use crate::AppState; use actix_multipart::Multipart; use actix_web::{get, post, web, Error, HttpResponse}; @@ -19,11 +19,7 @@ pub async fn post_secure_file( // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); // remove expired pastas (including this one if needed) remove_expired(&mut pastas); @@ -32,7 +28,7 @@ pub async fn post_secure_file( let mut index: usize = 0; let mut found: bool = false; for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { + if comparator(pasta) { index = i; found = true; break; @@ -86,11 +82,7 @@ pub async fn get_file( // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - let id_intern = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); // remove expired pastas (including this one if needed) remove_expired(&mut pastas); @@ -99,7 +91,7 @@ pub async fn get_file( let mut index: usize = 0; let mut found: bool = false; for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id_intern { + if comparator(pasta) { index = i; found = true; break; diff --git a/src/endpoints/list.rs b/src/endpoints/list.rs index 8d7f3a64..78ee8a0b 100644 --- a/src/endpoints/list.rs +++ b/src/endpoints/list.rs @@ -21,7 +21,7 @@ pub async fn list(data: web::Data) -> HttpResponse { .finish(); } - let mut pastas = data.pastas.lock().unwrap(); + let mut pastas: std::sync::MutexGuard<'_, Vec> = data.pastas.lock().unwrap(); remove_expired(&mut pastas); diff --git a/src/endpoints/pasta.rs b/src/endpoints/pasta.rs index ee7916d8..76e49927 100644 --- a/src/endpoints/pasta.rs +++ b/src/endpoints/pasta.rs @@ -1,9 +1,8 @@ use crate::args::{Args, ARGS}; use crate::endpoints::errors::ErrorTemplate; use crate::pasta::Pasta; -use crate::util::animalnumbers::to_u64; use crate::util::db::update; -use crate::util::hashids::to_u64 as hashid_to_u64; +use crate::util::hashids::alias_comparator; use crate::util::misc::remove_expired; use crate::AppState; use actix_multipart::Multipart; @@ -27,12 +26,8 @@ fn pastaresponse( ) -> HttpResponse { // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); + let comparator = alias_comparator(id.as_str()); - 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); @@ -41,7 +36,7 @@ fn pastaresponse( let mut index: usize = 0; let mut found: bool = false; for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { + if comparator(pasta) { index = i; found = true; break; @@ -173,11 +168,7 @@ fn urlresponse(data: web::Data, id: web::Path) -> HttpResponse // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); // remove expired pastas (including this one if needed) remove_expired(&mut pastas); @@ -187,7 +178,7 @@ fn urlresponse(data: web::Data, id: web::Path) -> HttpResponse let mut found: bool = false; for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { + if comparator(pasta) { index = i; found = true; break; @@ -255,11 +246,8 @@ pub async fn getrawpasta( // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); + // remove expired pastas (including this one if needed) remove_expired(&mut pastas); @@ -268,7 +256,7 @@ pub async fn getrawpasta( let mut index: usize = 0; let mut found: bool = false; for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { + if comparator(pasta) { index = i; found = true; break; @@ -336,11 +324,7 @@ pub async fn postrawpasta( // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); // remove expired pastas (including this one if needed) remove_expired(&mut pastas); @@ -349,7 +333,7 @@ pub async fn postrawpasta( let mut index: usize = 0; let mut found: bool = false; for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { + if comparator(pasta) { index = i; found = true; break; diff --git a/src/endpoints/qr.rs b/src/endpoints/qr.rs index 785f7105..7f46a46d 100644 --- a/src/endpoints/qr.rs +++ b/src/endpoints/qr.rs @@ -1,8 +1,7 @@ use crate::args::{Args, ARGS}; 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::hashids::alias_comparator; use crate::util::misc::{self, remove_expired}; use crate::AppState; use actix_web::{get, web, HttpResponse}; @@ -21,11 +20,7 @@ pub async fn getqr(data: web::Data, id: web::Path) -> HttpResp // get access to the pasta collection let mut pastas = data.pastas.lock().unwrap(); - let u64_id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); // remove expired pastas (including this one if needed) remove_expired(&mut pastas); @@ -34,7 +29,7 @@ pub async fn getqr(data: web::Data, id: web::Path) -> HttpResp let mut index: usize = 0; let mut found: bool = false; for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == u64_id { + if comparator(pasta) { index = i; found = true; break; diff --git a/src/endpoints/remove.rs b/src/endpoints/remove.rs index 644fcab8..38dffa84 100644 --- a/src/endpoints/remove.rs +++ b/src/endpoints/remove.rs @@ -5,9 +5,8 @@ use futures::TryStreamExt; use crate::args::ARGS; use crate::endpoints::errors::ErrorTemplate; use crate::pasta::PastaFile; -use crate::util::animalnumbers::to_u64; use crate::util::db::delete; -use crate::util::hashids::to_u64 as hashid_to_u64; +use crate::util::hashids::alias_comparator; use crate::util::misc::{decrypt, remove_expired}; use crate::AppState; use askama::Template; @@ -17,14 +16,11 @@ use std::fs; pub async fn remove(data: web::Data, id: web::Path) -> HttpResponse { let mut pastas = data.pastas.lock().unwrap(); - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { + if comparator(pasta) { + let id = pasta.id; // if it's encrypted or read-only, it needs password to be deleted if pasta.encrypt_server || pasta.readonly { return HttpResponse::Found() @@ -84,11 +80,7 @@ pub async fn post_remove( id: web::Path, mut payload: Multipart, ) -> Result { - let id = if ARGS.hash_ids { - hashid_to_u64(&id).unwrap_or(0) - } else { - to_u64(&id.into_inner()).unwrap_or(0) - }; + let comparator = alias_comparator(id.as_str()); let mut pastas = data.pastas.lock().unwrap(); @@ -105,7 +97,8 @@ pub async fn post_remove( } for (i, pasta) in pastas.iter().enumerate() { - if pasta.id == id { + if comparator(pasta) { + let id = pasta.id; if pastas[i].readonly || pastas[i].encrypt_server { if password != *"" { let res = decrypt(pastas[i].content.to_owned().as_str(), &password); diff --git a/src/pasta.rs b/src/pasta.rs index 6352ad31..a375651c 100644 --- a/src/pasta.rs +++ b/src/pasta.rs @@ -58,6 +58,7 @@ pub struct Pasta { pub id: u64, pub content: String, pub file: Option, + pub custom_alias: Option, pub extension: String, pub private: bool, pub readonly: bool, @@ -75,11 +76,11 @@ pub struct Pasta { impl Pasta { pub fn id_as_animals(&self) -> String { - if ARGS.hash_ids { + self.custom_alias.clone().unwrap_or(if ARGS.hash_ids { to_hashids(self.id) } else { to_animal_names(self.id) - } + }) } pub fn has_file(&self) -> bool { diff --git a/src/util/db_sqlite.rs b/src/util/db_sqlite.rs index 5eab1f83..8fc9f934 100644 --- a/src/util/db_sqlite.rs +++ b/src/util/db_sqlite.rs @@ -42,7 +42,8 @@ pub fn rewrite_all_to_db(pasta_data: &[Pasta]) { last_read INTEGER NOT NULL, read_count INTEGER NOT NULL, burn_after_reads INTEGER NOT NULL, - pasta_type TEXT NOT NULL + pasta_type TEXT NOT NULL, + custom_alias TEXT );", params![], ) @@ -67,8 +68,9 @@ pub fn rewrite_all_to_db(pasta_data: &[Pasta]) { last_read, read_count, burn_after_reads, - pasta_type - ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)", + pasta_type, + custom_alias + ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18)", params![ pasta.id, pasta.content, @@ -87,6 +89,7 @@ pub fn rewrite_all_to_db(pasta_data: &[Pasta]) { pasta.read_count, pasta.burn_after_reads, pasta.pasta_type, + pasta.custom_alias.as_ref().map_or("", |a| a.as_str()), ], ) .expect("Failed to insert pasta."); @@ -116,7 +119,8 @@ pub fn select_all_from_db() -> Vec { last_read INTEGER NOT NULL, read_count INTEGER NOT NULL, burn_after_reads INTEGER NOT NULL, - pasta_type TEXT NOT NULL + pasta_type TEXT NOT NULL, + custom_alias TEXT );", params![], ) @@ -157,6 +161,7 @@ pub fn select_all_from_db() -> Vec { read_count: row.get(14)?, burn_after_reads: row.get(15)?, pasta_type: row.get(16)?, + custom_alias: row.get(17)?, }) }) .expect("Failed to select Pastas from SQLite database."); @@ -189,7 +194,8 @@ pub fn insert(pasta: &Pasta) { last_read INTEGER NOT NULL, read_count INTEGER NOT NULL, burn_after_reads INTEGER NOT NULL, - pasta_type TEXT NOT NULL + pasta_type TEXT NOT NULL, + custom_alias TEXT );", params![], ) @@ -213,8 +219,9 @@ pub fn insert(pasta: &Pasta) { last_read, read_count, burn_after_reads, - pasta_type - ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)", + pasta_type, + custom_alias + ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18)", params![ pasta.id, pasta.content, @@ -233,6 +240,7 @@ pub fn insert(pasta: &Pasta) { pasta.read_count, pasta.burn_after_reads, pasta.pasta_type, + pasta.custom_alias.as_ref().map_or("", |a| a.as_str()), ], ) .expect("Failed to insert pasta."); @@ -259,7 +267,8 @@ pub fn update(pasta: &Pasta) { last_read = ?14, read_count = ?15, burn_after_reads = ?16, - pasta_type = ?17 + pasta_type = ?17, + custom_alias = ?18 WHERE id = ?1;", params![ pasta.id, @@ -279,6 +288,7 @@ pub fn update(pasta: &Pasta) { pasta.read_count, pasta.burn_after_reads, pasta.pasta_type, + pasta.custom_alias.as_ref().map_or("", |a| a.as_str()), ], ) .expect("Failed to update pasta."); diff --git a/src/util/hashids.rs b/src/util/hashids.rs index 0e53f313..444ad931 100644 --- a/src/util/hashids.rs +++ b/src/util/hashids.rs @@ -1,6 +1,10 @@ +use crate::{args::ARGS, util::hashids::to_u64 as hashid_to_u64}; +use crate::util::animalnumbers::to_u64 as animal_to_u64; use harsh::Harsh; use lazy_static::lazy_static; +use crate::pasta::Pasta; + lazy_static! { pub static ref HARSH: Harsh = Harsh::builder().length(6).build().unwrap(); } @@ -16,3 +20,15 @@ pub fn to_u64(hash_id: &str) -> Result { let id = ids.first().ok_or("No ID found in hash ID")?; Ok(*id) } + +pub fn alias_comparator(id: &str) -> Box bool> { + let raw_id = id.to_string(); + let id = if ARGS.hash_ids { + hashid_to_u64(id).unwrap_or(0) + } else { + animal_to_u64(id).unwrap_or(0) + }; + return Box::new(move |pasta|{ + pasta.id == id || (ARGS.enable_custom_url && pasta.custom_alias.as_ref() == Some(&raw_id)) + }); +} diff --git a/templates/guide.html b/templates/guide.html index 4a961a7b..14ff8234 100644 --- a/templates/guide.html +++ b/templates/guide.html @@ -78,5 +78,15 @@

Level 5: Secret

password never even leave your device. This option requires you to enter your password many times when accessing your data, but is extremely safe.

+ +

Custom Url

+
+

+ Use the custom url field to set a password for your upload. This will allow + you to access the it with a custom url. +

+

+ Note: Avoid using custom combinations that exist in default names (such as animal names). +

{% include "footer.html" %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 043f4ba4..783e33ac 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,5 +1,8 @@ {% include "header.html" %}
+ {% if args.enable_custom_url %} + + {%- endif %}
@@ -175,6 +178,13 @@
{%- endif %} + {% if args.enable_custom_url %} +
+
+ +
+ {%- endif %} +
@@ -219,11 +229,13 @@ const form = document.getElementById("pasta-form"); const submitButton = document.getElementById("submit-button"); const passwordField = document.getElementById("password_field"); + const aliasField = document.getElementById("alias_field"); const privacyDropdown = document.getElementById("privacy"); const contentInput = document.getElementById("content-input"); const content = document.getElementById("content"); const attachFileButton = document.getElementById('attach-file-button'); const dropContainer = document.getElementById('pasta-form'); + const hiddenCustomAlias = document.getElementById("custom_alias"); const hiddenFileButton = document.getElementById('file'); const hiddenRandomKeyField = document.getElementById("random_key"); const hiddenEncryptedRandomKeyField = document.getElementById("encrypted_random_key"); @@ -234,7 +246,11 @@ form.onsubmit = async function (event) { event.preventDefault(); // prevent default form submission - + // {% if args.enable_custom_url %} + if (aliasField.value.trim() != "") { + hiddenCustomAlias.value = aliasField.value.trim(); + } + // {%- endif %} // {% if args.encryption_client_side || args.encryption_server_side || args.enable_readonly %} if (passwordField.value.trim() != "") { if (fileOversized()) return false; @@ -296,6 +312,8 @@ window.location.href = xhr.responseURL; } else { console.log('Request failed with status:', xhr.status); + window.alert(xhr.responseText); + submitButton.disabled = false; } } };