diff --git a/.env.example b/.env.example index cdf5579..7423d1f 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,6 @@ PORT=8080 DATABASE_URL=target/database.db POOL_SIZE=4 FILES_DIR=target/static/ -MAX_FILESIZE=50000000 PASSWD=a1b2c3d4 diff --git a/README.md b/README.md index 0033370..da2b56f 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,6 @@ database_url = "database.db" pool_size = 4 # Path to the directory where files will be stored, relative or absolute files_dir = "files" -# Max allowed size for file uploads, in bytes -max_filesize = 10000000 # Highlight.js configuration [highlight] diff --git a/resources/index.html b/resources/index.html index 9c669af..b2aecd5 100644 --- a/resources/index.html +++ b/resources/index.html @@ -359,35 +359,30 @@ return; } - let fileReader = new FileReader(); - fileReader.onload = () => { - const id = urlInput.value; - const url = `${baseUrl}f/${id}`; + const fd = new FormData(); + fd.append("file", file); + const id = urlInput.value; + const url = `${baseUrl}f/${id}`; - const base64 = btoa(fileReader.result); - const filename = file.name; - let status; - fetch(url, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ base64, filename }), + let status; + fetch(url, { + method: "PUT", + body: fd, + }) + .then((response) => { + status = response.status; + return response.text(); }) - .then((response) => { - status = response.status; - return response.text(); - }) - .then((text) => { - if (status !== 201) { - throw new Error(text); - } else { - openModal(url); - clearInputs(); - fetchUsed(); - } - }) - .catch((error) => alert(error)); - }; - fileReader.readAsBinaryString(file); + .then((text) => { + if (status !== 201) { + throw new Error(text); + } else { + openModal(url); + clearInputs(); + fetchUsed(); + } + }) + .catch((error) => alert(error)); }); } else if (group === "links") { submitButton.addEventListener("click", () => { @@ -398,7 +393,6 @@ let status; fetch(url, { method: "PUT", - headers: { "Content-Type": "application/json" }, body: JSON.stringify({ forward }), }) .then((response) => { @@ -426,7 +420,6 @@ let status; fetch(url, { method: "PUT", - headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contents, highlight }), }) .then((response) => { diff --git a/src/main.rs b/src/main.rs index f6f97ad..35212c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ extern crate serde; extern crate diesel_migrations; use actix_identity::{CookieIdentityPolicy, IdentityService}; -use actix_web::{web, App, FromRequest, HttpServer}; +use actix_web::{web, App, HttpServer}; use diesel::{ r2d2::{self, ConnectionManager}, sqlite::SqliteConnection, @@ -53,7 +53,7 @@ async fn main() { #[cfg(not(feature = "dev"))] { embedded_migrations::run(&pool.get().unwrap()).unwrap_or_else(|e| { - eprintln!("Can't prepare database: {}.", e); + eprintln!("Can't prepare database: {}", e); process::exit(1); }); } @@ -76,8 +76,6 @@ async fn main() { }; let port = config.port; - let max_filesize_json = (config.max_filesize as f64 * 1.37) as usize; - println!("Listening on port {}", port); HttpServer::new(move || { @@ -111,9 +109,6 @@ async fn main() { ) .service( web::resource("/f/{id}") - .data(web::Json::::configure(|cfg| { - cfg.limit(max_filesize_json) - })) .route(web::get().to(routes::files::get)) .route(web::put().to(routes::files::put)) .route(web::delete().to(routes::files::delete)), @@ -133,13 +128,13 @@ async fn main() { }) .bind(&format!("localhost:{}", port)) .unwrap_or_else(|e| { - eprintln!("Can't bind webserver to specified port: {}.", e); + eprintln!("Can't bind webserver to specified port: {}", e); process::exit(1); }) .run() .await .unwrap_or_else(|e| { - eprintln!("Can't start webserver: {}.", e); + eprintln!("Can't start webserver: {}", e); process::exit(1); }); } diff --git a/src/routes.rs b/src/routes.rs index 44a4ffc..6c3349e 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -83,20 +83,8 @@ async fn auth( } } -/// Match result from REPLACE queries for PUT routes -fn match_replace_result_put( - result: Result>, -) -> Result { - match result { - Ok(x) => Ok(HttpResponse::Created().json(x)), - Err(_) => Err(HttpResponse::InternalServerError() - .body("Internal server error") - .into()), - } -} - -/// Match result from REPLACE queries for POST routes -fn match_replace_result_post( +/// Match result from REPLACE queries +fn match_replace_result( result: Result>, id: i32, ) -> Result { @@ -286,6 +274,7 @@ pub async fn logout(identity: Identity) -> impl Responder { } pub mod files { + use crate::routes::match_replace_result; use crate::{ queries::{self, SelectQuery}, routes::{auth, match_find_error, parse_id}, @@ -328,17 +317,15 @@ pub mod files { } } - /// Request body when PUTting files - #[derive(Deserialize)] - pub struct PutFile { - pub base64: String, - pub filename: String, - } - - /// Common setup for both PUT and POST - async fn setup(config: &Config) -> Result<(PathBuf, PathBuf), Error> { - let path = config.files_dir.clone(); - let relative_path = PathBuf::new(); + /// Common code for PUT and POST routes + async fn put_post( + id: i32, + mut body: Multipart, + pool: web::Data, + config: web::Data, + ) -> Result { + let mut path = config.files_dir.clone(); + let mut relative_path = PathBuf::new(); let dir_path = path.clone(); if web::block(move || fs::create_dir_all(dir_path)) .await @@ -349,78 +336,6 @@ pub mod files { .into()); } - Ok((path, relative_path)) - } - /// Common conversion for both PUT and POST - fn pts(path: &PathBuf) -> Result { - match path.to_str() { - Some(rp) => Ok(rp.to_owned()), - None => Err(HttpResponse::InternalServerError() - .body("Internal server error") - .into()), - } - } - - /// PUT a new file entry - pub async fn put( - request: HttpRequest, - path: web::Path, - body: web::Json, - pool: web::Data, - config: web::Data, - identity: Identity, - password_hash: web::Data>, - ) -> Result { - auth(identity, request, &password_hash).await?; - - let id = parse_id(&path)?; - let (mut path, mut relative_path) = setup(&config).await?; - - let mut filename = body.filename.clone(); - filename = format!("{:x}.{}", Utc::now().timestamp(), filename); - path.push(&filename); - relative_path.push(&filename); - let relative_path = pts(&relative_path)?; - - let contents = match web::block(move || base64::decode(&body.base64)).await { - Ok(contents) => contents, - Err(_) => { - return Err(HttpResponse::BadRequest() - .body("Invalid base64 encoded file") - .into()) - } - }; - if web::block(move || fs::write(&path, contents)) - .await - .is_err() - { - return Err(HttpResponse::InternalServerError() - .body("Internal server error") - .into()); - } - - match web::block(move || queries::files::replace(id, &relative_path, pool)).await { - Ok(file) => Ok(HttpResponse::Created().json(file)), - Err(_) => Err(HttpResponse::InternalServerError() - .body("Internal server error") - .into()), - } - } - - /// POST a new file entry using a multipart body - pub async fn post( - request: HttpRequest, - mut body: Multipart, - pool: web::Data, - config: web::Data, - identity: Identity, - password_hash: web::Data>, - ) -> Result { - auth(identity, request, &password_hash).await?; - - let id = random_id(&pool).await?; - let (mut path, mut relative_path) = setup(&config).await?; - let mut field = match body.next().await { Some(f) => f?, None => { @@ -444,7 +359,14 @@ pub mod files { let filename = format!("{:x}.{}", Utc::now().timestamp(), filename); path.push(&filename); relative_path.push(&filename); - let relative_path = pts(&relative_path)?; + let relative_path = match path.to_str() { + Some(rp) => rp.to_owned(), + None => { + return Err(HttpResponse::InternalServerError() + .body("Internal server error") + .into()) + } + }; let mut f = match web::block(move || File::create(&path)).await { Ok(f) => f, @@ -479,12 +401,39 @@ pub mod files { }; } - match web::block(move || queries::files::replace(id, &relative_path, pool)).await { - Ok(_) => Ok(HttpResponse::Created().body(format!("{}", radix_fmt::radix_36(id)))), - Err(_) => Err(HttpResponse::InternalServerError() - .body("Internal server error") - .into()), - } + match_replace_result( + web::block(move || queries::files::replace(id, &relative_path, pool)).await, + id, + ) + } + + /// PUT a new file entry + pub async fn put( + request: HttpRequest, + path: web::Path, + body: Multipart, + pool: web::Data, + config: web::Data, + identity: Identity, + password_hash: web::Data>, + ) -> Result { + auth(identity, request, &password_hash).await?; + let id = parse_id(&path)?; + put_post(id, body, pool, config).await + } + + /// POST a new file entry using a multipart body + pub async fn post( + request: HttpRequest, + body: Multipart, + pool: web::Data, + config: web::Data, + identity: Identity, + password_hash: web::Data>, + ) -> Result { + auth(identity, request, &password_hash).await?; + let id = random_id(&pool).await?; + put_post(id, body, pool, config).await } } @@ -492,8 +441,7 @@ pub mod links { use crate::{ queries::{self, SelectQuery}, routes::{ - auth, match_find_error, match_replace_result_post, match_replace_result_put, parse_id, - timestamp_to_last_modified, + auth, match_find_error, match_replace_result, parse_id, timestamp_to_last_modified, }, Pool, }; @@ -535,10 +483,10 @@ pub mod links { password_hash: web::Data>, ) -> Result { auth(identity, request, &password_hash).await?; - let id = parse_id(&path)?; - match_replace_result_put( + match_replace_result( web::block(move || queries::links::replace(id, &body.forward, pool)).await, + id, ) } @@ -551,9 +499,8 @@ pub mod links { password_hash: web::Data>, ) -> Result { auth(identity, request, &password_hash).await?; - let id = random_id(&pool).await?; - match_replace_result_post( + match_replace_result( web::block(move || queries::links::replace(id, &body.forward, pool)).await, id, ) @@ -565,8 +512,7 @@ pub mod texts { use crate::{ queries::{self, SelectQuery}, routes::{ - auth, match_find_error, match_replace_result_post, match_replace_result_put, parse_id, - timestamp_to_last_modified, + auth, match_find_error, match_replace_result, parse_id, timestamp_to_last_modified, }, Pool, }; @@ -636,11 +582,11 @@ pub mod texts { password_hash: web::Data>, ) -> Result { auth(identity, request, &password_hash).await?; - let id = parse_id(&path)?; - match_replace_result_put( + match_replace_result( web::block(move || queries::texts::replace(id, &body.contents, body.highlight, pool)) .await, + id, ) } @@ -653,9 +599,8 @@ pub mod texts { password_hash: web::Data>, ) -> Result { auth(identity, request, &password_hash).await?; - let id = random_id(&pool).await?; - match_replace_result_post( + match_replace_result( web::block(move || queries::texts::replace(id, &body.contents, body.highlight, pool)) .await, id, diff --git a/src/setup.rs b/src/setup.rs index 95fe2c6..138d628 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -86,8 +86,6 @@ pub struct Config { pub pool_size: u32, /// Directory where to store static files pub files_dir: PathBuf, - /// Maximum allowed file size - pub max_filesize: usize, /// Highlight.js configuration pub highlight: HighlightConfig, } @@ -113,14 +111,12 @@ impl Default for Config { }; let pool_size = std::cmp::max(1, num_cpus::get() as u32 / 2); let files_dir = get_data_dir().join("files"); - let max_filesize = 10_000_000; Self { port, database_url, pool_size, files_dir, - max_filesize, highlight: HighlightConfig::default(), } } @@ -211,14 +207,12 @@ impl Config { .expect("Invalid FILES_DIR") } }; - let max_filesize = parse_env!("MAX_FILESIZE"); Self { port, database_url, pool_size, files_dir, - max_filesize, highlight: HighlightConfig::default(), } }