diff --git a/filed/src/web/curlapi.rs b/filed/src/web/curlapi.rs new file mode 100644 index 0000000..35fa226 --- /dev/null +++ b/filed/src/web/curlapi.rs @@ -0,0 +1,9 @@ +use warp::{Filter, reply::Reply, reject::Rejection}; + +use super::state::SharedState; + +mod upload; + +pub fn get_routes(state: SharedState) -> impl Filter + Clone { + upload::get_routes(state) +} \ No newline at end of file diff --git a/filed/src/web/curlapi/upload.rs b/filed/src/web/curlapi/upload.rs new file mode 100644 index 0000000..a702bd9 --- /dev/null +++ b/filed/src/web/curlapi/upload.rs @@ -0,0 +1,123 @@ +use std::net::IpAddr; + +use warp::{filters::multipart::FormData, reply::{Reply, with_status}, reject::Rejection, Filter}; +use warp_real_ip::real_ip; + +use crate::{web::{state::SharedState, forms::{FormElement, UploadFormData}, rejection::HttpReject}, files::File}; + +pub async fn upload(form: FormData, ip: Option, state: SharedState) -> Result, Rejection> { + if ! state.config.files.allow_uploads { + return Ok( + Box::new( + with_status( + match state.config.files.upload_disable_reason { + Some(reason) => format!("Uploads are disabled for the following reason:\n{reason}"), + None => "Uploads are disabled.".into() + }, + warp::http::StatusCode::SERVICE_UNAVAILABLE + ) + ) + ) + } + + let params = FormElement::from_formdata(form) + .await + .map_err(|x| HttpReject::WarpError(x))?; + + let formdata = UploadFormData::from_formdata(params, true); + if let Some(formdata) = formdata { + + let mut breaks_conf = false; + if (!state.config.files.allow_custom_names) && formdata.filename.is_some() { + breaks_conf = true; + } + if (!state.config.files.allow_pass_protection) && formdata.password.is_some() { + breaks_conf = true; + } + + if breaks_conf { + return Ok( + Box::new( + with_status( + "Attempt to set name or password when they are disabled".to_string(), + warp::http::StatusCode::BAD_REQUEST + ) + ) + ); + } + + if let Some(pass) = state.config.files.upload_pass { + let mut pass_valid = false; + if let Some(upass) = formdata.instancepass { + pass_valid = upass == pass; + } else { + pass_valid = false + } + + if ! pass_valid { + return Ok( + Box::new( + with_status( + "Invalid instance password".to_string(), + warp::http::StatusCode::BAD_REQUEST + ) + ) + ); + } + } + + let file = File::create( + formdata.file, + formdata.mime, + formdata.filename.clone(), + state.env.clone(), + formdata.delmode, + formdata.password, + ip + ).await.map_err(|x| HttpReject::StringError(x.to_string()))?; + + state.file_mgr.save(&file, formdata.lookup_kind).map_err(|x| HttpReject::StringError(x.to_string()))?; + + return Ok( + Box::new( + format!( + concat!( + "File uploaded successfully.\n", + "It is available via this link:\n\n", + + "{}/upload/{}" + ), + state.env.instanceurl, + urlencoding::encode( + match formdata.filename { + Some(name) => name, + None => file.hash() + }.as_str() + ) + ) + ) + ); + + } else { + Ok( + Box::new( + with_status( + "Invalid form".to_string(), + warp::http::StatusCode::BAD_REQUEST + ) + ) + ) + } +} + +pub fn get_routes(state: SharedState) -> impl Filter + Clone { + warp::any() + .and(warp::path!("curlapi" / "upload")) + .and(warp::multipart::form()) + .and(real_ip(vec![state.env.proxy_addr])) + .and( + warp::any() + .map(move || state.clone()) + ) + .and_then(upload) +} \ No newline at end of file diff --git a/filed/src/web/forms.rs b/filed/src/web/forms.rs index 2d43aae..a8adc81 100644 --- a/filed/src/web/forms.rs +++ b/filed/src/web/forms.rs @@ -17,7 +17,7 @@ use crate::files::{File, lookup::LookupKind, DeleteMode}; use super::{state::SharedState, pages::{UploadSuccessPage, ErrorPage}, rejection::HttpReject}; #[derive(Debug, Serialize, Clone)] -struct FormElement { +pub struct FormElement { data: Vec, mime: String } @@ -58,15 +58,15 @@ impl FormElement { } } -struct UploadFormData { - filename: Option, - password: Option, - instancepass: Option, - lookup_kind: LookupKind, - delmode: DeleteMode, - file: Vec, - mime: String, - tos_consent: bool +pub struct UploadFormData { + pub filename: Option, + pub password: Option, + pub instancepass: Option, + pub lookup_kind: LookupKind, + pub delmode: DeleteMode, + pub file: Vec, + pub mime: String, + pub tos_consent: bool } impl Default for UploadFormData { @@ -86,7 +86,7 @@ impl Default for UploadFormData { impl UploadFormData { - pub fn from_formdata(data: HashMap) -> Option { + pub fn from_formdata(data: HashMap, use_defaults: bool) -> Option { let mut out = Self::default(); // Add a name @@ -125,7 +125,9 @@ impl UploadFormData { } }, None => { - return None + if ! use_defaults { + return None + } } } @@ -169,7 +171,7 @@ pub async fn upload(form: FormData, ip: Option, state: SharedState) -> R } let params: HashMap = FormElement::from_formdata(form).await.map_err(|x| HttpReject::WarpError(x))?; - let formdata = UploadFormData::from_formdata(params.clone()); + let formdata = UploadFormData::from_formdata(params.clone(), false); if let Some(formdata) = formdata { diff --git a/filed/src/web/mod.rs b/filed/src/web/mod.rs index 2fc92a3..bbc786d 100644 --- a/filed/src/web/mod.rs +++ b/filed/src/web/mod.rs @@ -8,16 +8,18 @@ use warp::{Filter, reply::Reply, reject::Rejection}; use crate::{env::Env, files::lookup::FileManager, config::types::Config}; mod pages; -mod forms; +pub mod forms; pub mod state; mod rejection; mod api; mod uploaded; +mod curlapi; use state::SharedState; pub fn routes(state: SharedState) -> impl Filter + Clone { static_dir!("static") + .or(curlapi::get_routes(state.clone())) .or(pages::get_routes(state.clone())) .or(forms::get_routes(state.clone())) .or(api::get_routes(state.clone()))