diff --git a/src/config.rs b/src/config.rs index 2a22ab3ef2422415b2a4dac1148b8f0a83a1c3f0..4d5f54d53af0ecaa36b86edaef77750e7dd17f51 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 de51352413282ea0a2e90808dc70b00d316cd13c..d910f67c2be7bb6a7b18fa562cd9a266ab1f3fb6 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 54d5181fd8a537b6e4d38cec3da1bc5f80702c72..21c710d8e45f515339ff33185218ffca42e5d8b4 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 dd302e8d05012d4bfb629473dc21ba0fd5fb8424..8e70f9d7539d46aadf33beac11697518412a683d 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()) }