aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Cargo.toml2
-rw-r--r--src/file_upload.rs100
-rw-r--r--src/listing.rs1
-rw-r--r--src/main.rs7
-rw-r--r--src/renderer.rs8
5 files changed, 116 insertions, 2 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 19b826e..5384ee7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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))
}