diff options
Diffstat (limited to 'src/file_upload.rs')
-rw-r--r-- | src/file_upload.rs | 139 |
1 files changed, 65 insertions, 74 deletions
diff --git a/src/file_upload.rs b/src/file_upload.rs index f9bf002..136bd45 100644 --- a/src/file_upload.rs +++ b/src/file_upload.rs @@ -1,13 +1,12 @@ use actix_web::{ - dev, http::{header, StatusCode}, - multipart, FutureResponse, HttpMessage, HttpRequest, HttpResponse, + HttpRequest, HttpResponse, }; -use futures::{future, future::FutureResult, Future, Stream}; +use futures::{future, Future, FutureExt, Stream, TryStreamExt}; use std::{ - fs, io::Write, path::{Component, PathBuf}, + pin::Pin, }; use crate::errors::{self, ContextualError}; @@ -17,12 +16,12 @@ use crate::themes::ColorScheme; /// Create future to save file. fn save_file( - field: multipart::Field<dev::Payload>, + field: actix_multipart::Field, file_path: PathBuf, overwrite_files: bool, -) -> Box<dyn Future<Item = i64, Error = ContextualError>> { +) -> Pin<Box<dyn Future<Output = Result<i64, ContextualError>>>> { if !overwrite_files && file_path.exists() { - return Box::new(future::err(ContextualError::CustomError( + return Box::pin(future::err(ContextualError::CustomError( "File already exists, and the overwrite_files option has not been set".to_string(), ))); } @@ -30,85 +29,75 @@ fn save_file( let mut file = match std::fs::File::create(&file_path) { Ok(file) => file, Err(e) => { - return Box::new(future::err(ContextualError::IOError( + return Box::pin(future::err(ContextualError::IOError( format!("Failed to create {}", file_path.display()), e, ))); } }; - Box::new( + Box::pin( field .map_err(ContextualError::MultipartError) - .fold(0i64, move |acc, bytes| { + .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::result(rt) + future::ready(rt) }), ) } /// Create new future to handle file as multipart data. fn handle_multipart( - item: multipart::MultipartItem<dev::Payload>, + field: actix_multipart::Field, mut file_path: PathBuf, overwrite_files: bool, -) -> Box<dyn Stream<Item = i64, Error = ContextualError>> { - match item { - multipart::MultipartItem::Field(field) => { - let filename = field - .headers() - .get(header::CONTENT_DISPOSITION) +) -> Pin<Box<dyn Stream<Item = Result<i64, 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) - .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::new(future::err(e).into_stream()); - match filename { - Ok(f) => { - match 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(), - )); - } + .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(), + )); } - file_path = file_path.join(f); - Box::new(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(), - )), + 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()) } - multipart::MultipartItem::Nested(mp) => Box::new( - mp.map_err(ContextualError::MultipartError) - .map(move |item| handle_multipart(item, file_path.clone(), overwrite_files)) - .flatten(), - ), + Err(e) => err(e( + "HTTP header".to_string(), + "Failed to retrieve the name of the file to upload".to_string(), + )), } } @@ -118,17 +107,19 @@ fn handle_multipart( /// invalid. /// This method returns future. pub fn upload_file( - req: &HttpRequest<crate::MiniserveConfig>, + req: HttpRequest, + payload: actix_web::web::Payload, default_color_scheme: ColorScheme, uses_random_route: bool, -) -> FutureResponse<HttpResponse> { +) -> Pin<Box<dyn Future<Output = 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() } else { "/".to_string() }; - let query_params = listing::extract_query_parameters(req); + let query_params = listing::extract_query_parameters(&req); let color_scheme = query_params.theme.unwrap_or(default_color_scheme); let upload_path = match query_params.path.clone() { Some(path) => match path.strip_prefix(Component::RootDir) { @@ -139,7 +130,7 @@ pub fn upload_file( let err = ContextualError::InvalidHTTPRequestError( "Missing query parameter 'path'".to_string(), ); - return Box::new(create_error_response( + return Box::pin(create_error_response( &err.to_string(), StatusCode::BAD_REQUEST, &return_path, @@ -152,14 +143,14 @@ pub fn upload_file( } }; - let app_root_dir = match req.state().path.canonicalize() { + let app_root_dir = match conf.path.canonicalize() { Ok(dir) => dir, Err(e) => { let err = ContextualError::IOError( "Failed to resolve path served by miniserve".to_string(), e, ); - return Box::new(create_error_response( + return Box::pin(create_error_response( &err.to_string(), StatusCode::INTERNAL_SERVER_ERROR, &return_path, @@ -179,7 +170,7 @@ pub fn upload_file( let err = ContextualError::InvalidHTTPRequestError( "Invalid value for 'path' parameter".to_string(), ); - return Box::new(create_error_response( + return Box::pin(create_error_response( &err.to_string(), StatusCode::BAD_REQUEST, &return_path, @@ -191,13 +182,13 @@ pub fn upload_file( )); } }; - let overwrite_files = req.state().overwrite_files; - Box::new( - req.multipart() + let overwrite_files = conf.overwrite_files; + Box::pin( + actix_multipart::Multipart::new(req.headers(), payload) .map_err(ContextualError::MultipartError) - .map(move |item| handle_multipart(item, target_dir.clone(), overwrite_files)) - .flatten() - .collect() + .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() @@ -229,7 +220,7 @@ fn create_error_response( color_scheme: ColorScheme, default_color_scheme: ColorScheme, uses_random_route: bool, -) -> FutureResult<HttpResponse, actix_web::error::Error> { +) -> future::Ready<Result<HttpResponse, actix_web::Error>> { errors::log_error_chain(description.to_string()); future::ok( HttpResponse::BadRequest() |