diff --git a/Cargo.lock b/Cargo.lock index 20e5ecb..f037087 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,7 +129,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "breeze" -version = "0.1.1" +version = "0.1.2" dependencies = [ "archived", "async-recursion", diff --git a/Cargo.toml b/Cargo.toml index 932fb46..f1aabe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breeze" -version = "0.1.1" +version = "0.1.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/engine.rs b/src/engine.rs index e20d913..557e102 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -20,16 +20,17 @@ use tokio::{ use tokio_stream::StreamExt; use walkdir::WalkDir; -use crate::view::{ViewSuccess, ViewError}; +use crate::view::{ViewError, ViewSuccess}; pub struct Engine { // state - cache: RwLock, // in-memory cache. note/ i plan to lock the cache specifically only when needed rather than locking the whole struct + cache: RwLock, // in-memory cache pub upl_count: AtomicUsize, // cached count of uploaded files // config - pub base_url: String, // base url for formatting upload urls - save_path: PathBuf, // where uploads are saved to disk + pub base_url: String, // base url for formatting upload urls + save_path: PathBuf, // where uploads are saved to disk + pub upload_key: String, // authorisation key for uploading new files cache_max_length: usize, // if an upload is bigger than this size, it won't be cached } @@ -39,6 +40,7 @@ impl Engine { pub fn new( base_url: String, save_path: PathBuf, + upload_key: String, cache_max_length: usize, cache_lifetime: Duration, cache_full_scan_freq: Duration, // how often the cache will be scanned for expired items @@ -54,6 +56,7 @@ impl Engine { base_url, save_path, + upload_key, cache_max_length, } @@ -141,11 +144,11 @@ impl Engine { // create file to save upload to let mut file = File::create(path) .await - .expect("could not open file! make sure your upload path exists"); + .expect("could not open file! make sure your upload path is valid"); // receive chunks and save them to file while let Some(chunk) = rx.recv().await { - debug!(target: "process_upload", "writing chunk to disk (length: {})", chunk.len()); + debug!("writing chunk to disk (length: {})", chunk.len()); file.write_all(&chunk) .await .expect("error while writing file to disk"); @@ -157,15 +160,15 @@ impl Engine { let chunk = chunk.unwrap(); // send chunk to io task - debug!(target: "process_upload", "sending data to io task"); + debug!("sending data to io task"); tx.send(chunk.clone()) .await .expect("failed to send data to io task"); if use_cache { - debug!(target: "process_upload", "receiving data into buffer"); + debug!("receiving data into buffer"); if data.len() + chunk.len() > data.capacity() { - error!(target: "process_upload", "the amount of data sent exceeds the content-length provided by the client! caching will be cancelled for this upload."); + error!("the amount of data sent exceeds the content-length provided by the client! caching will be cancelled for this upload."); // if we receive too much data, drop the buffer and stop using cache (it is still okay to use disk, probably) data = BytesMut::new(); @@ -180,10 +183,12 @@ impl Engine { if use_cache { let mut cache = self.cache.write().await; - info!(target: "process_upload", "caching upload!"); + info!("caching upload!"); cache.insert(name, data.freeze()); } + info!("finished processing upload!!"); + // if all goes well, increment the cached upload counter self.upl_count.fetch_add(1, Ordering::Relaxed); } @@ -228,7 +233,7 @@ impl Engine { let cached_data = self.read_cached_upload(&name).await; if let Some(data) = cached_data { - info!(target: "get_upload", "got upload from cache!!"); + info!("got upload from cache!!"); return Ok(ViewSuccess::FromCache(data)); } else { @@ -241,7 +246,7 @@ impl Engine { .expect("failed to read upload file metadata") .len() as usize; - debug!(target: "get_upload", "read upload from disk, size = {}", length); + debug!("read upload from disk, size = {}", length); // if the upload is okay to cache, recache it and send a fromcache response if self.will_use_cache(length) { @@ -268,12 +273,12 @@ impl Engine { let mut cache = self.cache.write().await; cache.insert(name, data.clone()); - info!(target: "get_upload", "recached upload from disk!"); + info!(/* */"recached upload from disk!"); return Ok(ViewSuccess::FromCache(data)); } - info!(target: "get_upload", "got upload from disk!"); + info!("got upload from disk!"); return Ok(ViewSuccess::FromDisk(file)); } diff --git a/src/main.rs b/src/main.rs index ebf0463..3783fa6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ async fn main() { // read env vars let base_url = env::var("BRZ_BASE_URL").expect("missing BRZ_BASE_URL! base url for upload urls (ex: http://127.0.0.1:8000 for http://127.0.0.1:8000/p/abcdef.png, http://picture.wtf for http://picture.wtf/p/abcdef.png)"); let save_path = env::var("BRZ_SAVE_PATH").expect("missing BRZ_SAVE_PATH! this should be a path where uploads are saved to disk (ex: /srv/uploads, C:\\brzuploads)"); + let upload_key = env::var("BRZ_UPLOAD_KEY").unwrap_or_default(); let cache_max_length = env::var("BRZ_CACHE_UPL_MAX_LENGTH").expect("missing BRZ_CACHE_UPL_MAX_LENGTH! this is the max length an upload can be in bytes before it won't be cached (ex: 80000000 for 80MB)"); let cache_upl_lifetime = env::var("BRZ_CACHE_UPL_LIFETIME").expect("missing BRZ_CACHE_UPL_LIFETIME! this indicates how long an upload will stay in cache (ex: 1800 for 30 minutes, 60 for 1 minute)"); let cache_scan_freq = env::var("BRZ_CACHE_SCAN_FREQ").expect("missing BRZ_CACHE_SCAN_FREQ! this is the frequency of full cache scans, which scan for and remove expired uploads (ex: 60 for 1 minute)"); @@ -39,13 +40,19 @@ async fn main() { let cache_mem_capacity = usize::from_str_radix(&cache_mem_capacity, 10).expect("failed parsing BRZ_CACHE_MEM_CAPACITY! it should be a positive number without any separators"); if !save_path.exists() || !save_path.is_dir() { - panic!("the save path does not exist or is not a directory. this is invalid"); + panic!("the save path does not exist or is not a directory! this is invalid"); + } + + if upload_key.is_empty() { + // i would prefer this to be a warning but the default log level hides those + error!("upload key is empty! no key will be required for uploading new files"); } // create engine let engine = Engine::new( base_url, save_path, + upload_key, cache_max_length, cache_upl_lifetime, cache_scan_freq, @@ -71,7 +78,7 @@ async fn shutdown_signal() { let ctrl_c = async { signal::ctrl_c() .await - .expect("failed to add ctrl-c handler"); + .expect("failed to add SIGINT handler"); }; #[cfg(unix)] diff --git a/src/new.rs b/src/new.rs index 579dc43..2f864c3 100644 --- a/src/new.rs +++ b/src/new.rs @@ -4,7 +4,7 @@ use axum::{ extract::{BodyStream, Query, State}, http::HeaderValue, }; -use hyper::{HeaderMap, StatusCode, header}; +use hyper::{header, HeaderMap, StatusCode}; #[axum::debug_handler] pub async fn new( @@ -13,12 +13,21 @@ pub async fn new( Query(params): Query>, stream: BodyStream, ) -> Result { - if !params.contains_key("name") { + let original_name = params.get("name"); + + // the original file name wasn't given, so i can't work out what the extension should be + if original_name.is_none() { return Err(StatusCode::BAD_REQUEST); } - let original_name = params.get("name").unwrap(); - let original_path = PathBuf::from(original_name); + let key = params.get("key"); + + // check upload key, if i need to + if !engine.upload_key.is_empty() && key.unwrap_or(&String::new()) != &engine.upload_key { + return Err(StatusCode::FORBIDDEN); + } + + let original_path = PathBuf::from(original_name.unwrap()); let path = engine.gen_path(&original_path).await; let name = path @@ -29,6 +38,7 @@ pub async fn new( let url = format!("{}/p/{}", engine.base_url, name); + // read and parse content-length, and if it fails just assume it's really high so it doesn't cache let content_length = headers .get(header::CONTENT_LENGTH) .unwrap_or(&HeaderValue::from_static("")) @@ -37,9 +47,10 @@ pub async fn new( .unwrap() .unwrap_or(usize::MAX); + // pass it off to the engine to be processed! engine .process_upload(path, name, content_length, stream) .await; - + Ok(url) } diff --git a/src/view.rs b/src/view.rs index 9828ae1..68ad5d9 100644 --- a/src/view.rs +++ b/src/view.rs @@ -20,8 +20,8 @@ pub enum ViewSuccess { } pub enum ViewError { - NotFound, - InternalServerError, + NotFound, // 404 + InternalServerError, // 500 } impl IntoResponse for ViewSuccess { @@ -80,9 +80,10 @@ pub async fn view( .into_iter() .any(|x| !matches!(x, Component::Normal(_))) { - warn!(target: "view", "a request attempted path traversal"); + warn!("a request attempted path traversal"); return Err(ViewError::NotFound); } + // get result from the engine! engine.get_upload(&original_path).await }