diff options
author | Vojtěch Pejša <vojtechpejsa7@gmail.com> | 2019-03-24 09:25:48 +0000 |
---|---|---|
committer | Vojtěch Pejša <vojtechpejsa7@gmail.com> | 2019-04-04 08:50:54 +0000 |
commit | 66c1c10d39e6ecb212ec4709888493693339c07d (patch) | |
tree | 3c1ff76f042520c60f5d31085ddabfca18d20541 | |
parent | Merge pull request #57 from svenstaro/dependabot/cargo/serde-1.0.90 (diff) | |
download | miniserve-66c1c10d39e6ecb212ec4709888493693339c07d.tar.gz miniserve-66c1c10d39e6ecb212ec4709888493693339c07d.zip |
Implement file upload.
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/file_upload.rs | 100 | ||||
-rw-r--r-- | src/listing.rs | 1 | ||||
-rw-r--r-- | src/main.rs | 7 | ||||
-rw-r--r-- | src/renderer.rs | 8 |
5 files changed, 116 insertions, 2 deletions
@@ -42,4 +42,4 @@ bytes = "0.4.12" futures = "0.1.26" libflate = "0.1.21" failure = "0.1.5" -log = "0.4.6"
\ No newline at end of file +log = "0.4.6" 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(""))) + } +} diff --git a/src/listing.rs b/src/listing.rs index c4daf88..0173176 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -265,6 +265,7 @@ pub fn directory_listing<S>( page_parent, sort_method, sort_order, + &base.to_string_lossy(), ) .into_string(), )) diff --git a/src/main.rs b/src/main.rs index f662a73..37a3226 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![feature(proc_macro_hygiene)] +use actix_web::http::Method; use actix_web::{fs, middleware, server, App}; use clap::crate_version; use simplelog::{Config, LevelFilter, TermLogger}; @@ -13,6 +14,7 @@ mod archive; mod args; mod auth; mod errors; +mod file_upload; mod listing; mod renderer; @@ -195,6 +197,11 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> { let random_route = app.state().random_route.clone().unwrap_or_default(); let full_route = format!("/{}", random_route); + //allow file upload + let app = app.resource("/upload", |r| { + r.method(Method::POST).f(file_upload::upload_file) + }); + if let Some(s) = s { // Handle directories app.handler(&full_route, s) diff --git a/src/renderer.rs b/src/renderer.rs index 66fc714..3039dc7 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -14,12 +14,18 @@ pub fn page( page_parent: Option<String>, sort_method: Option<listing::SortingMethod>, sort_order: Option<listing::SortingOrder>, + base: &str, ) -> Markup { html! { (page_header(page_title)) body { span #top { } - h1.title { (page_title) } + h1 { (page_title) } + form action={"/upload?path=" (base)} method="POST" enctype="multipart/form-data" { + p { "Select file to upload" } + input type="file" name="file_to_upload" {} + input type="submit" value="Upload file" {} + } div.download { (archive_button(archive::CompressionMethod::TarGz)) } |