aboutsummaryrefslogtreecommitdiffstats
path: root/src/file_upload.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/file_upload.rs')
-rw-r--r--src/file_upload.rs100
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("")))
+ }
+}