diff --git a/filed/src/web/api/files/upload.rs b/filed/src/web/api/files/upload.rs index 4ca0e7e..28a9b8f 100644 --- a/filed/src/web/api/files/upload.rs +++ b/filed/src/web/api/files/upload.rs @@ -1,9 +1,12 @@ -use std::collections::HashMap; +use std::{collections::HashMap, net::IpAddr}; 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_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}; @@ -16,6 +19,7 @@ struct UploadAPIMetadata { struct UploadAPIPayload { file: Vec, + file_type: String, instance_pass: Option, metadata: UploadAPIMetadata } @@ -24,6 +28,7 @@ impl Default for UploadAPIPayload { fn default() -> Self { Self { file: vec![], + file_type: "application/octet-stream".into(), instance_pass: None, metadata: UploadAPIMetadata { sha512: "".into(), @@ -56,6 +61,8 @@ impl UploadAPIPayload { fields_set = true; } } + + out.file_type = file.mime.clone(); } // optional ones @@ -73,7 +80,7 @@ impl UploadAPIPayload { } } -pub async fn upload(state: SharedState, data: FormData) -> Result, Rejection> { +pub async fn upload(state: SharedState, data: FormData, ip: Option) -> Result, Rejection> { let data = FormElement::from_formdata(data) .await; @@ -100,7 +107,6 @@ pub async fn upload(state: SharedState, data: FormData) -> Result let payload = UploadAPIPayload::from_form(data); if let Some(payload) = payload { - if is_api_pass(&state) { if let Err(res) = check_api_pass( &state, @@ -113,6 +119,93 @@ pub async fn upload(state: SharedState, data: FormData) -> Result } } + // 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( @@ -129,9 +222,13 @@ pub async fn upload(state: SharedState, data: FormData) -> Result } pub fn upload_f(state: SharedState) -> impl Filter + Clone { + + let proxy = state.env.proxy_addr.clone(); + warp::path!("api" / "files" / "upload") .and(warp::post()) .map(move || state.clone()) .and(warp::multipart::form()) + .and(real_ip(vec![proxy])) .and_then(upload) }