From c8e193f19a80a22c35d2e05457cb3fb71e255400 Mon Sep 17 00:00:00 2001
From: Pavel Kirilin <win10@list.ru>
Date: Sun, 23 Jan 2022 17:18:29 +0400
Subject: [PATCH] Fixed concatenation ext. Description: * Fixed HEAD request to
 file; * Added option to remove part files;

Signed-off-by: Pavel Kirilin <win10@list.ru>
---
 src/config.rs                      |  7 ++++
 src/protocol/core/routes.rs        | 51 ++++++++++++++++++++++++------
 src/protocol/creation/routes.rs    | 13 ++++++--
 src/protocol/termination/routes.rs |  4 +--
 4 files changed, 60 insertions(+), 15 deletions(-)

diff --git a/src/config.rs b/src/config.rs
index 2a22ab3..4d5f54d 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -183,6 +183,13 @@ pub struct RustusConf {
     )]
     pub tus_extensions: Vec<Extensions>,
 
+    /// Remove part files after concatenation is done.
+    /// By default rustus does nothing with part files after concatenation.
+    ///
+    /// This parameter is only needed if concatenation extension is enabled.
+    #[structopt(long, parse(from_flag))]
+    pub remove_parts: bool,
+
     #[structopt(flatten)]
     pub storage_opts: StorageOptions,
 
diff --git a/src/protocol/core/routes.rs b/src/protocol/core/routes.rs
index de51352..d910f67 100644
--- a/src/protocol/core/routes.rs
+++ b/src/protocol/core/routes.rs
@@ -16,7 +16,7 @@ pub fn server_info(app_conf: web::Data<RustusConf>) -> HttpResponse {
         .join(",");
     HttpResponse::Ok()
         .insert_header(("Tus-Extension", ext_str.as_str()))
-        .body("")
+        .finish()
 }
 
 pub async fn get_file_info(
@@ -25,17 +25,43 @@ pub async fn get_file_info(
 ) -> actix_web::Result<HttpResponse> {
     // Getting file id from URL.
     if request.match_info().get("file_id").is_none() {
-        return Ok(HttpResponse::NotFound().body(""));
+        return Ok(HttpResponse::NotFound().body("No file id provided."));
     }
     let file_id = request.match_info().get("file_id").unwrap();
 
     // Getting file info from info_storage.
     let file_info = state.info_storage.get_info(file_id).await?;
     if file_info.storage != state.data_storage.to_string() {
-        return Ok(HttpResponse::NotFound().body(""));
+        return Ok(HttpResponse::NotFound().body("File not found."));
     }
     let mut builder = HttpResponse::Ok();
+    if file_info.is_partial {
+        builder.insert_header(("Upload-Concat", "partial"));
+    }
+    if file_info.is_final && file_info.parts.is_some() {
+        #[allow(clippy::or_fun_call)]
+        let parts = file_info
+            .parts
+            .clone()
+            .unwrap()
+            .iter()
+            .map(|file| {
+                format!(
+                    "{}/{}",
+                    state
+                        .config
+                        .base_url()
+                        .strip_suffix('/')
+                        .unwrap_or(state.config.base_url().as_str()),
+                    file.as_str()
+                )
+            })
+            .collect::<Vec<String>>()
+            .join(" ");
+        builder.insert_header(("Upload-Concat", format!("final; {}", parts)));
+    }
     builder
+        .no_chunking(file_info.offset as u64)
         .insert_header(("Upload-Offset", file_info.offset.to_string()))
         .insert_header(("Content-Length", file_info.offset.to_string()));
     // Upload length is known.
@@ -47,7 +73,7 @@ pub async fn get_file_info(
     if let Some(meta) = file_info.get_metadata_string() {
         builder.insert_header(("Upload-Metadata", meta));
     }
-    Ok(builder.body(""))
+    Ok(builder.finish())
 }
 
 pub async fn write_bytes(
@@ -58,17 +84,17 @@ pub async fn write_bytes(
     // Checking if request has required headers.
     let check_content_type = |val: &str| val == "application/offset+octet-stream";
     if !check_header(&request, "Content-Type", check_content_type) {
-        return Ok(HttpResponse::UnsupportedMediaType().body(""));
+        return Ok(HttpResponse::UnsupportedMediaType().body("Unknown content-type."));
     }
     // Getting current offset.
     let offset: Option<usize> = parse_header(&request, "Upload-Offset");
 
     if offset.is_none() {
-        return Ok(HttpResponse::UnsupportedMediaType().body(""));
+        return Ok(HttpResponse::UnsupportedMediaType().body("No offset provided."));
     }
 
     if request.match_info().get("file_id").is_none() {
-        return Ok(HttpResponse::NotFound().body(""));
+        return Ok(HttpResponse::NotFound().body("No file id provided."));
     }
 
     // New upload length.
@@ -87,13 +113,18 @@ pub async fn write_bytes(
     // Getting file info.
     let mut file_info = state.info_storage.get_info(file_id).await?;
 
+    // According to TUS protocol you can't update final uploads.
+    if file_info.is_final {
+        return Ok(HttpResponse::Forbidden().finish());
+    }
+
     // Checking if file was stored in the same storage.
     if file_info.storage != state.data_storage.to_string() {
-        return Ok(HttpResponse::NotFound().body(""));
+        return Ok(HttpResponse::NotFound().finish());
     }
     // Checking if offset from request is the same as the real offset.
     if offset.unwrap() != file_info.offset {
-        return Ok(HttpResponse::Conflict().body(""));
+        return Ok(HttpResponse::Conflict().finish());
     }
 
     // If someone want to update file length.
@@ -152,5 +183,5 @@ pub async fn write_bytes(
     }
     Ok(HttpResponse::NoContent()
         .insert_header(("Upload-Offset", file_info.offset.to_string()))
-        .body(""))
+        .finish())
 }
diff --git a/src/protocol/creation/routes.rs b/src/protocol/creation/routes.rs
index 54d5181..21c710d 100644
--- a/src/protocol/creation/routes.rs
+++ b/src/protocol/creation/routes.rs
@@ -116,6 +116,7 @@ pub async fn create_file(
         if is_final {
             file_info.is_final = true;
             file_info.parts = Some(get_upload_parts(&request));
+            file_info.deferred_size = false;
         }
         if is_partial {
             file_info.is_partial = true;
@@ -158,10 +159,16 @@ pub async fn create_file(
         }
         state
             .data_storage
-            .concat_files(&file_info, parts_info)
+            .concat_files(&file_info, parts_info.clone())
             .await?;
         file_info.offset = final_size;
         file_info.length = Some(final_size);
+        if state.config.remove_parts {
+            for part in parts_info {
+                state.data_storage.remove_file(&part).await?;
+                state.info_storage.remove_info(part.id.as_str()).await?;
+            }
+        }
     }
 
     // Create upload URL for this file.
@@ -175,7 +182,7 @@ pub async fn create_file(
     if with_upload && !bytes.is_empty() && !(concat_ext && is_final) {
         let octet_stream = |val: &str| val == "application/offset+octet-stream";
         if !check_header(&request, "Content-Type", octet_stream) {
-            return Ok(HttpResponse::BadRequest().body(""));
+            return Ok(HttpResponse::BadRequest().finish());
         }
         // Writing first bytes.
         state
@@ -207,5 +214,5 @@ pub async fn create_file(
     Ok(HttpResponse::Created()
         .insert_header(("Location", upload_url.as_str()))
         .insert_header(("Upload-Offset", file_info.offset.to_string()))
-        .body(""))
+        .finish())
 }
diff --git a/src/protocol/termination/routes.rs b/src/protocol/termination/routes.rs
index dd302e8..8e70f9d 100644
--- a/src/protocol/termination/routes.rs
+++ b/src/protocol/termination/routes.rs
@@ -16,7 +16,7 @@ pub async fn terminate(
     if let Some(file_id) = file_id_opt {
         let file_info = state.info_storage.get_info(file_id.as_str()).await?;
         if file_info.storage != state.data_storage.to_string() {
-            return Ok(HttpResponse::NotFound().body(""));
+            return Ok(HttpResponse::NotFound().finish());
         }
         state.info_storage.remove_info(file_id.as_str()).await?;
         state.data_storage.remove_file(&file_info).await?;
@@ -35,5 +35,5 @@ pub async fn terminate(
             });
         }
     }
-    Ok(HttpResponse::NoContent().body(""))
+    Ok(HttpResponse::NoContent().finish())
 }
-- 
GitLab