add all the functionality into the upload api

This commit is contained in:
blek 2023-12-12 17:01:29 +10:00
parent ae90e5474b
commit 7ad7a5acf0
Signed by: blek
GPG Key ID: 14546221E3595D0C
1 changed files with 101 additions and 4 deletions

View File

@ -1,9 +1,12 @@
use std::collections::HashMap; use std::{collections::HashMap, net::IpAddr};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use serde_json::json;
use sha2::{Sha512, Digest, digest::FixedOutput};
use warp::{reply::{Reply, with_status, json}, http::StatusCode, reject::Rejection, Filter, filters::multipart::FormData}; use warp::{reply::{Reply, with_status, json}, http::StatusCode, reject::Rejection, Filter, filters::multipart::FormData};
use warp_real_ip::real_ip;
use crate::web::{state::SharedState, forms::FormElement, rejection::HttpReject, api::types::{ErrorMessage, Error}}; use crate::{web::{state::SharedState, forms::FormElement, api::types::{ErrorMessage, Error}}, files::{File, lookup::LookupKind}};
use super::{is_api_pass, check_api_pass}; use super::{is_api_pass, check_api_pass};
@ -16,6 +19,7 @@ struct UploadAPIMetadata {
struct UploadAPIPayload { struct UploadAPIPayload {
file: Vec<u8>, file: Vec<u8>,
file_type: String,
instance_pass: Option<String>, instance_pass: Option<String>,
metadata: UploadAPIMetadata metadata: UploadAPIMetadata
} }
@ -24,6 +28,7 @@ impl Default for UploadAPIPayload {
fn default() -> Self { fn default() -> Self {
Self { Self {
file: vec![], file: vec![],
file_type: "application/octet-stream".into(),
instance_pass: None, instance_pass: None,
metadata: UploadAPIMetadata { metadata: UploadAPIMetadata {
sha512: "".into(), sha512: "".into(),
@ -56,6 +61,8 @@ impl UploadAPIPayload {
fields_set = true; fields_set = true;
} }
} }
out.file_type = file.mime.clone();
} }
// optional ones // optional ones
@ -73,7 +80,7 @@ impl UploadAPIPayload {
} }
} }
pub async fn upload(state: SharedState, data: FormData) -> Result<Box<dyn Reply>, Rejection> { pub async fn upload(state: SharedState, data: FormData, ip: Option<IpAddr>) -> Result<Box<dyn Reply>, Rejection> {
let data = FormElement::from_formdata(data) let data = FormElement::from_formdata(data)
.await; .await;
@ -100,7 +107,6 @@ pub async fn upload(state: SharedState, data: FormData) -> Result<Box<dyn Reply>
let payload = UploadAPIPayload::from_form(data); let payload = UploadAPIPayload::from_form(data);
if let Some(payload) = payload { if let Some(payload) = payload {
if is_api_pass(&state) { if is_api_pass(&state) {
if let Err(res) = check_api_pass( if let Err(res) = check_api_pass(
&state, &state,
@ -113,6 +119,93 @@ pub async fn upload(state: SharedState, data: FormData) -> Result<Box<dyn Reply>
} }
} }
// payload is all valid and accessible at this point
let mut hash: Sha512 = Sha512::new();
hash.update(&payload.file);
let hash = hex::encode(hash.finalize_fixed());
if hash != payload.metadata.sha512 {
return Ok(
Box::new(
with_status(
json(
&ErrorMessage {
error: Error::APIError,
details: Some("Hash does not match file".into())
}
),
StatusCode::BAD_REQUEST
)
)
)
}
let file = File::create(
payload.file,
payload.file_type,
payload.metadata.name.clone(),
state.env,
crate::files::DeleteMode::Time,
payload.metadata.pass,
ip
).await;
if let Err(err) = file {
return Ok(
Box::new(
with_status(
json(
&ErrorMessage {
error: Error::APIError,
details: Some(
format!("Error while saving the file: {err}")
)
}
),
StatusCode::INTERNAL_SERVER_ERROR
)
)
)
}
let file = file.unwrap();
let saved = state.file_mgr.save(
&file,
match payload.metadata.name {
Some(_) => LookupKind::ByName,
None => LookupKind::ByHash
}
);
if let Err(err) = saved {
return Ok(
Box::new(
with_status(
json(
&ErrorMessage {
error: Error::APIError,
details: Some(
format!("Error while saving the file: {err}")
)
}
),
StatusCode::INTERNAL_SERVER_ERROR
)
)
)
}
return Ok(
Box::new(
json(
&json!({
"status": "OK"
})
)
)
)
} }
Ok( Ok(
@ -129,9 +222,13 @@ pub async fn upload(state: SharedState, data: FormData) -> Result<Box<dyn Reply>
} }
pub fn upload_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { pub fn upload_f(state: SharedState) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
let proxy = state.env.proxy_addr.clone();
warp::path!("api" / "files" / "upload") warp::path!("api" / "files" / "upload")
.and(warp::post()) .and(warp::post())
.map(move || state.clone()) .map(move || state.clone())
.and(warp::multipart::form()) .and(warp::multipart::form())
.and(real_ip(vec![proxy]))
.and_then(upload) .and_then(upload)
} }