diff options
Diffstat (limited to '')
-rw-r--r-- | src/file_upload.rs | 233 |
1 files changed, 100 insertions, 133 deletions
diff --git a/src/file_upload.rs b/src/file_upload.rs index 5faa67f..6fa99ef 100644 --- a/src/file_upload.rs +++ b/src/file_upload.rs @@ -2,11 +2,10 @@ use actix_web::{ http::{header, StatusCode}, HttpRequest, HttpResponse, }; -use futures::{future, Future, FutureExt, Stream, TryStreamExt}; +use futures::TryStreamExt; use std::{ io::Write, path::{Component, PathBuf}, - pin::Pin, }; use crate::errors::{self, ContextualError}; @@ -14,88 +13,62 @@ use crate::listing::{self, SortingMethod, SortingOrder}; use crate::renderer; /// Create future to save file. -fn save_file( +async fn save_file( field: actix_multipart::Field, file_path: PathBuf, overwrite_files: bool, -) -> Pin<Box<dyn Future<Output = Result<i64, ContextualError>>>> { +) -> Result<u64, ContextualError> { if !overwrite_files && file_path.exists() { - return Box::pin(future::err(ContextualError::DuplicateFileError)); + return Err(ContextualError::DuplicateFileError); } - let mut file = match std::fs::File::create(&file_path) { - Ok(file) => file, - Err(e) => { - return Box::pin(future::err(ContextualError::IoError( - format!("Failed to create {}", file_path.display()), - e, - ))); - } - }; - Box::pin( - field - .map_err(ContextualError::MultipartError) - .try_fold(0i64, move |acc, bytes| { - let rt = file - .write_all(bytes.as_ref()) - .map(|_| acc + bytes.len() as i64) - .map_err(|e| { - ContextualError::IoError("Failed to write to file".to_string(), e) - }); - future::ready(rt) - }), - ) + let file = std::fs::File::create(&file_path).map_err(|e| { + ContextualError::IoError(format!("Failed to create {}", file_path.display()), e) + })?; + + let (_, written_len) = field + .map_err(ContextualError::MultipartError) + .try_fold((file, 0u64), |(mut file, written_len), bytes| async move { + file.write_all(bytes.as_ref()) + .map_err(|e| ContextualError::IoError("Failed to write to file".to_string(), e))?; + Ok((file, written_len + bytes.len() as u64)) + }) + .await?; + + Ok(written_len) } /// Create new future to handle file as multipart data. -fn handle_multipart( +async fn handle_multipart( field: actix_multipart::Field, - mut file_path: PathBuf, + file_path: PathBuf, overwrite_files: bool, -) -> Pin<Box<dyn Stream<Item = Result<i64, ContextualError>>>> { +) -> Result<u64, ContextualError> { let filename = field - .headers() - .get(header::CONTENT_DISPOSITION) - .ok_or(ContextualError::ParseError) - .and_then(|cd| { - header::ContentDisposition::from_raw(cd).map_err(|_| ContextualError::ParseError) - }) - .and_then(|content_disposition| { - content_disposition - .get_filename() - .ok_or(ContextualError::ParseError) - .map(String::from) - }); - let err = |e: ContextualError| Box::pin(future::err(e).into_stream()); - match filename { - Ok(f) => { - match std::fs::metadata(&file_path) { - Ok(metadata) => { - if !metadata.is_dir() { - return err(ContextualError::InvalidPathError(format!( - "cannot upload file to {}, since it's not a directory", - &file_path.display() - ))); - } else if metadata.permissions().readonly() { - return err(ContextualError::InsufficientPermissionsError( - file_path.display().to_string(), - )); - } - } - Err(_) => { - return err(ContextualError::InsufficientPermissionsError( - file_path.display().to_string(), - )); - } - } - file_path = file_path.join(f); - Box::pin(save_file(field, file_path, overwrite_files).into_stream()) - } - Err(e) => err(e( - "HTTP header".to_string(), - "Failed to retrieve the name of the file to upload".to_string(), + .content_disposition() + .and_then(|cd| cd.get_filename().map(String::from)) + .ok_or_else(|| { + ContextualError::ParseError( + "HTTP header".to_string(), + "Failed to retrieve the name of the file to upload".to_string(), + ) + })?; + + match std::fs::metadata(&file_path) { + Err(_) => Err(ContextualError::InsufficientPermissionsError( + file_path.display().to_string(), )), - } + Ok(metadata) if !metadata.is_dir() => Err(ContextualError::InvalidPathError(format!( + "cannot upload file to {}, since it's not a directory", + &file_path.display() + ))), + Ok(metadata) if metadata.permissions().readonly() => Err( + ContextualError::InsufficientPermissionsError(file_path.display().to_string()), + ), + Ok(_) => Ok(()), + }?; + + save_file(field, file_path.join(filename), overwrite_files).await } /// Handle incoming request to upload file. @@ -104,16 +77,16 @@ fn handle_multipart( /// invalid. /// This method returns future. #[allow(clippy::too_many_arguments)] -pub fn upload_file( +pub async fn upload_file( req: HttpRequest, payload: actix_web::web::Payload, uses_random_route: bool, favicon_route: String, css_route: String, - default_color_scheme: &str, - default_color_scheme_dark: &str, + default_color_scheme: String, + default_color_scheme_dark: String, hide_version_footer: bool, -) -> Pin<Box<dyn Future<Output = Result<HttpResponse, actix_web::Error>>>> { +) -> Result<HttpResponse, actix_web::Error> { let conf = req.app_data::<crate::MiniserveConfig>().unwrap(); let return_path = if let Some(header) = req.headers().get(header::REFERER) { header.to_str().unwrap_or("/").to_owned() @@ -131,7 +104,7 @@ pub fn upload_file( let err = ContextualError::InvalidHttpRequestError( "Missing query parameter 'path'".to_string(), ); - return Box::pin(create_error_response( + return Ok(create_error_response( &err.to_string(), StatusCode::BAD_REQUEST, &return_path, @@ -140,8 +113,8 @@ pub fn upload_file( uses_random_route, &favicon_route, &css_route, - default_color_scheme, - default_color_scheme_dark, + &default_color_scheme, + &default_color_scheme_dark, hide_version_footer, )); } @@ -154,7 +127,7 @@ pub fn upload_file( "Failed to resolve path served by miniserve".to_string(), e, ); - return Box::pin(create_error_response( + return Ok(create_error_response( &err.to_string(), StatusCode::INTERNAL_SERVER_ERROR, &return_path, @@ -163,8 +136,8 @@ pub fn upload_file( uses_random_route, &favicon_route, &css_route, - default_color_scheme, - default_color_scheme_dark, + &default_color_scheme, + &default_color_scheme_dark, hide_version_footer, )); } @@ -177,7 +150,7 @@ pub fn upload_file( let err = ContextualError::InvalidHttpRequestError( "Invalid value for 'path' parameter".to_string(), ); - return Box::pin(create_error_response( + return Ok(create_error_response( &err.to_string(), StatusCode::BAD_REQUEST, &return_path, @@ -186,8 +159,8 @@ pub fn upload_file( uses_random_route, &favicon_route, &css_route, - default_color_scheme, - default_color_scheme_dark, + &default_color_scheme, + &default_color_scheme_dark, hide_version_footer, )); } @@ -196,33 +169,29 @@ pub fn upload_file( let default_color_scheme = conf.default_color_scheme.clone(); let default_color_scheme_dark = conf.default_color_scheme_dark.clone(); - Box::pin( - actix_multipart::Multipart::new(req.headers(), payload) - .map_err(ContextualError::MultipartError) - .map_ok(move |item| handle_multipart(item, target_dir.clone(), overwrite_files)) - .try_flatten() - .try_collect::<Vec<_>>() - .then(move |e| match e { - Ok(_) => future::ok( - HttpResponse::SeeOther() - .append_header((header::LOCATION, return_path)) - .finish(), - ), - Err(e) => create_error_response( - &e.to_string(), - StatusCode::INTERNAL_SERVER_ERROR, - &return_path, - query_params.sort, - query_params.order, - uses_random_route, - &favicon_route, - &css_route, - &default_color_scheme, - &default_color_scheme_dark, - hide_version_footer, - ), - }), - ) + match actix_multipart::Multipart::new(req.headers(), payload) + .map_err(ContextualError::MultipartError) + .and_then(move |field| handle_multipart(field, target_dir.clone(), overwrite_files)) + .try_collect::<Vec<u64>>() + .await + { + Ok(_) => Ok(HttpResponse::SeeOther() + .append_header((header::LOCATION, return_path)) + .finish()), + Err(e) => Ok(create_error_response( + &e.to_string(), + StatusCode::INTERNAL_SERVER_ERROR, + &return_path, + query_params.sort, + query_params.order, + uses_random_route, + &favicon_route, + &css_route, + &default_color_scheme, + &default_color_scheme_dark, + hide_version_footer, + )), + } } /// Convenience method for creating response errors, if file upload fails. @@ -239,27 +208,25 @@ fn create_error_response( default_color_scheme: &str, default_color_scheme_dark: &str, hide_version_footer: bool, -) -> future::Ready<Result<HttpResponse, actix_web::Error>> { +) -> HttpResponse { errors::log_error_chain(description.to_string()); - future::ok( - HttpResponse::BadRequest() - .content_type("text/html; charset=utf-8") - .body( - renderer::render_error( - description, - error_code, - return_path, - sorting_method, - sorting_order, - true, - !uses_random_route, - favicon_route, - css_route, - default_color_scheme, - default_color_scheme_dark, - hide_version_footer, - ) - .into_string(), - ), - ) + HttpResponse::BadRequest() + .content_type("text/html; charset=utf-8") + .body( + renderer::render_error( + description, + error_code, + return_path, + sorting_method, + sorting_order, + true, + !uses_random_route, + favicon_route, + css_route, + default_color_scheme, + default_color_scheme_dark, + hide_version_footer, + ) + .into_string(), + ) } |