diff options
Diffstat (limited to '')
-rw-r--r-- | src/file_upload.rs | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/src/file_upload.rs b/src/file_upload.rs new file mode 100644 index 0000000..00cca17 --- /dev/null +++ b/src/file_upload.rs @@ -0,0 +1,100 @@ +use actix_web::{ + dev, error, + http::header::{ContentDisposition, LOCATION}, + multipart, Error, FutureResponse, HttpMessage, HttpRequest, HttpResponse, +}; +use std::io::Write; +use std::path::{Component, PathBuf}; + +use futures::future; +use futures::{Future, Stream}; + +pub fn save_file( + field: multipart::Field<dev::Payload>, + file_path: PathBuf, +) -> Box<Future<Item = i64, Error = Error>> { + let mut file = match std::fs::File::create(file_path) { + Ok(file) => file, + Err(e) => return Box::new(future::err(error::ErrorInternalServerError(e))), + }; + Box::new( + field + .fold(0i64, move |acc, bytes| { + let rt = file + .write_all(bytes.as_ref()) + .map(|_| acc + bytes.len() as i64) + .map_err(|e| error::MultipartError::Payload(error::PayloadError::Io(e))); + future::result(rt) + }) + .map_err(|e| error::ErrorInternalServerError(e)), + ) +} + +pub fn handle_multipart( + item: multipart::MultipartItem<dev::Payload>, + mut file_path: PathBuf, +) -> Box<Stream<Item = i64, Error = Error>> { + match item { + multipart::MultipartItem::Field(field) => { + let err = || Box::new(future::err(error::ContentTypeError::ParseError.into())); + let filename = field + .headers() + .get("content-disposition") + .ok_or(err()) + .and_then(|cd| ContentDisposition::from_raw(cd).map_err(|_| err())) + .and_then(|content_disposition| { + content_disposition + .get_filename() + .ok_or(err()) + .map(|cd| String::from(cd)) + }); + match filename { + Ok(f) => { + file_path = file_path.join(f); + // TODO should I allow overriding existing files? + Box::new(save_file(field, file_path).into_stream()) + } + Err(e) => Box::new(e.into_stream()), + } + } + multipart::MultipartItem::Nested(mp) => Box::new( + mp.map_err(error::ErrorInternalServerError) + .map(move |item| handle_multipart(item, file_path.clone())) + .flatten(), + ), + } +} + +pub fn upload_file(req: &HttpRequest<crate::MiniserveConfig>) -> FutureResponse<HttpResponse> { + if req.query().contains_key("path") { + let path_str = req.query()["path"].clone(); + let mut path = PathBuf::from(path_str.clone()); + while path.has_root() { + path = match path.strip_prefix(Component::RootDir) { + Ok(path) => path.to_path_buf(), + //TODO better error response + Err(_) => return Box::new(future::ok(HttpResponse::BadRequest().body(""))), + } + } + // TODO verify that path is under current dir + if let Ok(target_path) = path.join(req.state().path.clone()).canonicalize() { + Box::new( + req.multipart() + .map_err(error::ErrorInternalServerError) + .map(move |item| handle_multipart(item, target_path.clone())) + .flatten() + .collect() + .map(|_| { + HttpResponse::TemporaryRedirect() + .header(LOCATION, path_str) + .finish() + }) + .map_err(|e| e), + ) + } else { + Box::new(future::ok(HttpResponse::BadRequest().body(""))) + } + } else { + Box::new(future::ok(HttpResponse::BadRequest().body(""))) + } +} |