Implement cURL API #15

Merged
blek merged 7 commits from curlapi into 0.2-dev 2023-11-02 15:17:00 +01:00
4 changed files with 150 additions and 14 deletions
Showing only changes of commit 9ccd3d6212 - Show all commits

9
filed/src/web/curlapi.rs Normal file
View File

@ -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<Extract = impl Reply, Error = Rejection> + Clone {
upload::get_routes(state)
}

View File

@ -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<IpAddr>, state: SharedState) -> Result<Box<dyn Reply>, 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<Extract = impl Reply, Error = Rejection> + 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)
}

View File

@ -17,7 +17,7 @@ use crate::files::{File, lookup::LookupKind, DeleteMode};
use super::{state::SharedState, pages::{UploadSuccessPage, ErrorPage}, rejection::HttpReject}; use super::{state::SharedState, pages::{UploadSuccessPage, ErrorPage}, rejection::HttpReject};
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone)]
struct FormElement { pub struct FormElement {
data: Vec<u8>, data: Vec<u8>,
mime: String mime: String
} }
@ -58,15 +58,15 @@ impl FormElement {
} }
} }
struct UploadFormData { pub struct UploadFormData {
filename: Option<String>, pub filename: Option<String>,
password: Option<String>, pub password: Option<String>,
instancepass: Option<String>, pub instancepass: Option<String>,
lookup_kind: LookupKind, pub lookup_kind: LookupKind,
delmode: DeleteMode, pub delmode: DeleteMode,
file: Vec<u8>, pub file: Vec<u8>,
mime: String, pub mime: String,
tos_consent: bool pub tos_consent: bool
} }
impl Default for UploadFormData { impl Default for UploadFormData {
@ -86,7 +86,7 @@ impl Default for UploadFormData {
impl UploadFormData { impl UploadFormData {
pub fn from_formdata(data: HashMap<String, FormElement>) -> Option<UploadFormData> { pub fn from_formdata(data: HashMap<String, FormElement>, use_defaults: bool) -> Option<UploadFormData> {
let mut out = Self::default(); let mut out = Self::default();
// Add a name // Add a name
@ -125,9 +125,11 @@ impl UploadFormData {
} }
}, },
None => { None => {
if ! use_defaults {
return None return None
} }
} }
}
match data.get("instancepass") { match data.get("instancepass") {
Some(val) => { Some(val) => {
@ -169,7 +171,7 @@ pub async fn upload(form: FormData, ip: Option<IpAddr>, state: SharedState) -> R
} }
let params: HashMap<String, FormElement> = FormElement::from_formdata(form).await.map_err(|x| HttpReject::WarpError(x))?; let params: HashMap<String, FormElement> = 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 { if let Some(formdata) = formdata {

View File

@ -8,16 +8,18 @@ use warp::{Filter, reply::Reply, reject::Rejection};
use crate::{env::Env, files::lookup::FileManager, config::types::Config}; use crate::{env::Env, files::lookup::FileManager, config::types::Config};
mod pages; mod pages;
mod forms; pub mod forms;
pub mod state; pub mod state;
mod rejection; mod rejection;
mod api; mod api;
mod uploaded; mod uploaded;
mod curlapi;
use state::SharedState; use state::SharedState;
pub fn routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { pub fn routes(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
static_dir!("static") static_dir!("static")
.or(curlapi::get_routes(state.clone()))
.or(pages::get_routes(state.clone())) .or(pages::get_routes(state.clone()))
.or(forms::get_routes(state.clone())) .or(forms::get_routes(state.clone()))
.or(api::get_routes(state.clone())) .or(api::get_routes(state.clone()))