From 75f47e112c44fb66a885f418fac90736b51455df Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Wed, 8 Nov 2023 23:56:39 +0100 Subject: [PATCH 1/3] Added content-disposition. Signed-off-by: Pavel Kirilin --- src/info_storages/models/file_info.rs | 4 ++++ src/storages/file_storage.rs | 9 ++++++--- src/storages/s3_hybrid_storage.rs | 4 +++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/info_storages/models/file_info.rs b/src/info_storages/models/file_info.rs index d99dec1..905eba7 100644 --- a/src/info_storages/models/file_info.rs +++ b/src/info_storages/models/file_info.rs @@ -90,6 +90,10 @@ impl FileInfo { } } + pub fn get_filename(&self) -> &str { + self.metadata.get("filename").unwrap_or(&self.id) + } + pub async fn json(&self) -> RustusResult { let info_clone = self.clone(); tokio::task::spawn_blocking(move || { diff --git a/src/storages/file_storage.rs b/src/storages/file_storage.rs index 0427833..b386b41 100644 --- a/src/storages/file_storage.rs +++ b/src/storages/file_storage.rs @@ -1,4 +1,4 @@ -use std::{io::Write, path::PathBuf}; +use std::{fs::File, io::Write, path::PathBuf}; use actix_files::NamedFile; use actix_web::{HttpRequest, HttpResponse}; @@ -76,8 +76,11 @@ impl Storage for FileStorage { request: &HttpRequest, ) -> RustusResult { if let Some(path) = &file_info.path { - Ok(NamedFile::open_async(path) - .await + let file = File::open(path).map_err(|err| { + error!("{:?}", err); + RustusError::FileNotFound + })?; + Ok(NamedFile::from_file(file, file_info.get_filename()) .map_err(|err| { error!("{:?}", err); RustusError::FileNotFound diff --git a/src/storages/s3_hybrid_storage.rs b/src/storages/s3_hybrid_storage.rs index 17a2cf1..b45d308 100644 --- a/src/storages/s3_hybrid_storage.rs +++ b/src/storages/s3_hybrid_storage.rs @@ -7,7 +7,7 @@ use crate::{ use super::Storage; use crate::{storages::file_storage::FileStorage, utils::dir_struct::substr_time}; -use actix_web::{HttpRequest, HttpResponse, HttpResponseBuilder}; +use actix_web::{http::header::ContentDisposition, HttpRequest, HttpResponse, HttpResponseBuilder}; use async_trait::async_trait; use bytes::Bytes; use derive_more::Display; @@ -136,6 +136,8 @@ impl Storage for S3HybridStorage { let s3_request = Reqwest::new(&self.bucket, &key, command); let s3_response = s3_request.response().await?; let mut response = HttpResponseBuilder::new(actix_web::http::StatusCode::OK); + let disposition = ContentDisposition::attachment(file_info.get_filename()); + response.insert_header(disposition); Ok(response.streaming(s3_response.bytes_stream())) } From b545f42fb0e441d6fae1b3eda3cb6623617891e9 Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Thu, 9 Nov 2023 00:00:25 +0100 Subject: [PATCH 2/3] Fixed response generating for s3 storage. Signed-off-by: Pavel Kirilin --- src/storages/s3_hybrid_storage.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/storages/s3_hybrid_storage.rs b/src/storages/s3_hybrid_storage.rs index b45d308..7b079c0 100644 --- a/src/storages/s3_hybrid_storage.rs +++ b/src/storages/s3_hybrid_storage.rs @@ -137,8 +137,9 @@ impl Storage for S3HybridStorage { let s3_response = s3_request.response().await?; let mut response = HttpResponseBuilder::new(actix_web::http::StatusCode::OK); let disposition = ContentDisposition::attachment(file_info.get_filename()); - response.insert_header(disposition); - Ok(response.streaming(s3_response.bytes_stream())) + Ok(response + .insert_header(disposition) + .streaming(s3_response.bytes_stream())) } async fn add_bytes(&self, file_info: &FileInfo, bytes: Bytes) -> RustusResult<()> { From 1fd547c9caca09eb729cbd41f1f25e060dae8a5e Mon Sep 17 00:00:00 2001 From: Pavel Kirilin Date: Thu, 9 Nov 2023 00:35:08 +0100 Subject: [PATCH 3/3] Fixed content disposition for s3-hybrid storage. Signed-off-by: Pavel Kirilin --- Cargo.lock | 6 ++++-- Cargo.toml | 2 ++ src/storages/s3_hybrid_storage.rs | 7 ++++--- src/utils/headers.rs | 25 ++++++++++++++++++++++++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74b60fb..784183d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2067,9 +2067,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -3191,6 +3191,8 @@ dependencies = [ "log", "md-5", "mimalloc", + "mime", + "mime_guess", "openssl", "prometheus", "rbatis", diff --git a/Cargo.toml b/Cargo.toml index 9215083..0b084af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,8 @@ clap = { version = "4.1.8", features = ["derive", "env"] } dotenvy = { version = "0.15.6", features = ["clap"] } sentry = "0.30.0" sentry-actix = "0.30.0" +mime = "0.3.17" +mime_guess = "2.0.4" [dependencies.sha1] version = "^0.10.1" diff --git a/src/storages/s3_hybrid_storage.rs b/src/storages/s3_hybrid_storage.rs index 7b079c0..7c6139f 100644 --- a/src/storages/s3_hybrid_storage.rs +++ b/src/storages/s3_hybrid_storage.rs @@ -3,11 +3,13 @@ use std::{collections::HashMap, path::PathBuf}; use crate::{ errors::{RustusError, RustusResult}, info_storages::FileInfo, + utils::headers::generate_disposition, }; use super::Storage; use crate::{storages::file_storage::FileStorage, utils::dir_struct::substr_time}; -use actix_web::{http::header::ContentDisposition, HttpRequest, HttpResponse, HttpResponseBuilder}; + +use actix_web::{HttpRequest, HttpResponse, HttpResponseBuilder}; use async_trait::async_trait; use bytes::Bytes; use derive_more::Display; @@ -136,9 +138,8 @@ impl Storage for S3HybridStorage { let s3_request = Reqwest::new(&self.bucket, &key, command); let s3_response = s3_request.response().await?; let mut response = HttpResponseBuilder::new(actix_web::http::StatusCode::OK); - let disposition = ContentDisposition::attachment(file_info.get_filename()); Ok(response - .insert_header(disposition) + .insert_header(generate_disposition(file_info.get_filename())) .streaming(s3_response.bytes_stream())) } diff --git a/src/utils/headers.rs b/src/utils/headers.rs index 1bec0b8..5a2a268 100644 --- a/src/utils/headers.rs +++ b/src/utils/headers.rs @@ -1,6 +1,9 @@ use std::str::FromStr; -use actix_web::HttpRequest; +use actix_web::{ + http::header::{ContentDisposition, DispositionParam, DispositionType}, + HttpRequest, +}; /// Parse header's value. /// @@ -42,6 +45,26 @@ pub fn check_header(request: &HttpRequest, header_name: &str, expr: fn(&str) -> .unwrap_or(false) } +/// This function generates content disposition +/// based on file name. +pub fn generate_disposition(filename: &str) -> ContentDisposition { + let mime_type = mime_guess::from_path(filename).first_or_octet_stream(); + let disposition = match mime_type.type_() { + mime::IMAGE | mime::TEXT | mime::AUDIO | mime::VIDEO => DispositionType::Inline, + mime::APPLICATION => match mime_type.subtype() { + mime::JAVASCRIPT | mime::JSON => DispositionType::Inline, + name if name == "wasm" => DispositionType::Inline, + _ => DispositionType::Attachment, + }, + _ => DispositionType::Attachment, + }; + + ContentDisposition { + disposition, + parameters: vec![DispositionParam::Filename(String::from(filename))], + } +} + #[cfg(test)] mod tests { use super::{check_header, parse_header};