diff --git a/Cargo.lock b/Cargo.lock index 95d8175e20044dae16458ad848fed16fbb86d772..20ec430d0b31bcabad08e876effff098151690c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1973,18 +1973,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.131" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" +checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.131" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" +checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5f908aab2c50accf79b6613abd0dc33e956289b6..d9228585e5c98028f32737dde25a12f5b61eeaa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,4 +23,4 @@ serde_json = "1" log = "^0.4.14" url = "2.2.2" simple-logging = { version = "^2.0.2" } -sqlx = { version = "0.5", features = [ "runtime-async-std-native-tls", "sqlite" ] } \ No newline at end of file +sqlx = { version = "0.5", features = ["runtime-async-std-native-tls", "sqlite"] } \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 810ab5b74703cb37c17b32a52d0d9e49246482d0..6d0377285eb48504bd5dca17ceb31f45922f9345 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,10 +16,10 @@ pub struct StorageOptions { /// This directory is used to store files /// for all *file_storage storages. #[structopt( - long, - default_value = "./data", - required_if("storage", "file_storage"), - required_if("storage", "sqlite_file_storage") + long, + default_value = "./data", + required_if("storage", "file_storage"), + required_if("storage", "sqlite_file_storage") )] pub data: PathBuf, @@ -28,9 +28,9 @@ pub struct StorageOptions { /// This file is used to /// store information about uploaded files. #[structopt( - long, - default_value = "data/info.sqlite3", - required_if("storage", "sqlite_file_storage") + long, + default_value = "data/info.sqlite3", + required_if("storage", "sqlite_file_storage") )] pub sqlite_dsn: PathBuf, } @@ -64,9 +64,9 @@ pub struct TuserConf { /// Enabled extensions for TUS protocol. #[structopt( - long, - default_value = "creation,creation-with-upload", - env = "TUSER_EXTENSIONS" + long, + default_value = "creation,creation-with-upload,getting", + env = "TUSER_EXTENSIONS" )] pub extensions: String, @@ -80,6 +80,7 @@ pub enum ProtocolExtensions { CreationWithUpload, Creation, Termination, + Getting, } impl TryFrom<String> for ProtocolExtensions { @@ -93,6 +94,7 @@ impl TryFrom<String> for ProtocolExtensions { "creation" => Ok(ProtocolExtensions::Creation), "creation-with-upload" => Ok(ProtocolExtensions::CreationWithUpload), "termination" => Ok(ProtocolExtensions::Termination), + "getting" => Ok(ProtocolExtensions::Getting), _ => Err(TuserError::UnknownExtension(value.clone())), } } @@ -103,9 +105,10 @@ impl From<ProtocolExtensions> for String { /// original names. fn from(ext: ProtocolExtensions) -> Self { match ext { - ProtocolExtensions::Creation => Self::from("creation"), - ProtocolExtensions::CreationWithUpload => Self::from("creation-with-upload"), - ProtocolExtensions::Termination => Self::from("termination"), + ProtocolExtensions::Creation => "creation".into(), + ProtocolExtensions::CreationWithUpload => "creation-with-upload".into(), + ProtocolExtensions::Termination => "termination".into(), + ProtocolExtensions::Getting => "getting".into(), } } } diff --git a/src/errors.rs b/src/errors.rs index 60851b687f79b284261d3ae3d2a88bd5a621bc21..173b3feaa61d3d0634b35daad6a437032e2375ab 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -8,8 +8,8 @@ pub type TuserResult<T> = Result<T, TuserError>; #[derive(thiserror::Error, Debug)] pub enum TuserError { - #[error("File with id {0} was not found")] - FileNotFound(String), + #[error("Not found")] + FileNotFound, #[error("File with id {0} already exists")] FileAlreadyExists(String), #[error("Given offset is incorrect.")] @@ -40,12 +40,14 @@ impl From<TuserError> for Error { impl ResponseError for TuserError { fn error_response(&self) -> HttpResponse { - HttpResponseBuilder::new(self.status_code()).body(format!("{}", self)) + HttpResponseBuilder::new(self.status_code()) + .set_header("Content-Type", "text/html; charset=utf-8") + .body(format!("{}", self)) } fn status_code(&self) -> StatusCode { match self { - TuserError::FileNotFound(_) => StatusCode::NOT_FOUND, + TuserError::FileNotFound => StatusCode::NOT_FOUND, TuserError::WrongOffset => StatusCode::CONFLICT, _ => StatusCode::INTERNAL_SERVER_ERROR, } diff --git a/src/protocol/core/routes.rs b/src/protocol/core/routes.rs index 87e769bbb080bdef5a3678114233595e0f61ad47..9b5662244077758df94b8f30028853c0721f170a 100644 --- a/src/protocol/core/routes.rs +++ b/src/protocol/core/routes.rs @@ -30,6 +30,7 @@ pub async fn get_file_info( HttpResponseBuilder::new(StatusCode::OK) .set_header("Upload-Offset", file_info.offset.to_string()) .set_header("Upload-Length", file_info.length.to_string()) + .set_header("Content-Length", file_info.offset.to_string()) .body("") } else { HttpResponseBuilder::new(StatusCode::NOT_FOUND).body("") diff --git a/src/protocol/getting/mod.rs b/src/protocol/getting/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..a63ceb7935f12dbbd98feb7034a6bf00b3c75f5b --- /dev/null +++ b/src/protocol/getting/mod.rs @@ -0,0 +1,21 @@ +use actix_web::{guard, web}; + +use crate::TuserConf; + +mod routes; + +/// Add getting extension. +/// +/// This extension allows you +/// to get uploaded file. +/// +/// This is unofficial extension. +pub fn add_extension(web_app: &mut web::ServiceConfig, app_conf: &TuserConf) { + web_app.service( + // GET /base/file + web::resource(app_conf.file_url().as_str()) + .name("getting:get") + .guard(guard::Get()) + .to(routes::get_file), + ); +} diff --git a/src/protocol/getting/routes.rs b/src/protocol/getting/routes.rs new file mode 100644 index 0000000000000000000000000000000000000000..cdcebaff5f64a48cefc3c87b719fe7d5275d11be --- /dev/null +++ b/src/protocol/getting/routes.rs @@ -0,0 +1,16 @@ +use actix_web::{HttpRequest, Responder, web}; + +use crate::errors::TuserError; +use crate::Storage; + +pub async fn get_file( + request: HttpRequest, + storage: web::Data<Box<dyn Storage + Send + Sync>>, +) -> impl Responder { + let file_id_opt = request.match_info().get("file_id").map(String::from); + if let Some(file_id) = file_id_opt { + storage.get_contents(file_id.as_str()).await + } else { + Err(TuserError::FileNotFound) + } +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index ae3cd98d6729c610683c89e462c3fa675e4a45c3..64faf840ed90ce251826e7944e2d321ad7d6008f 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -6,6 +6,7 @@ use crate::TuserConf; mod core; mod creation; mod creation_with_upload; +mod getting; mod termination; /// Configure TUS web application. @@ -23,6 +24,9 @@ pub fn setup(app_conf: TuserConf) -> Box<dyn Fn(&mut web::ServiceConfig)> { ProtocolExtensions::Termination => { termination::add_extension(web_app, &app_conf); } + ProtocolExtensions::Getting => { + getting::add_extension(web_app, &app_conf); + } } } core::add_extension(web_app, &app_conf); diff --git a/src/routes.rs b/src/routes.rs index d510bfea02e86dffdb06c5a81b196876b01755e2..ce07d5828e87df3474b2f54be73ce62fde16385a 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,10 +1,9 @@ -use actix_web::dev::HttpResponseBuilder; -use actix_web::http::StatusCode; use actix_web::HttpResponse; +use crate::errors::{TuserError, TuserResult}; + /// Default response to all unknown URLs. -pub fn not_found() -> HttpResponse { - HttpResponseBuilder::new(StatusCode::NOT_FOUND) - .set_header("Content-Type", "text/html; charset=utf-8") - .body("Not found") +#[allow(clippy::unused_async)] +pub async fn not_found() -> TuserResult<HttpResponse> { + Err(TuserError::FileNotFound) } diff --git a/src/storages/file_storage.rs b/src/storages/file_storage.rs index 433033f687ca5bdf72e5c7cd3c8eaeac45ff0894..26d1fc77393f50726277c60941374d7449bd7928 100644 --- a/src/storages/file_storage.rs +++ b/src/storages/file_storage.rs @@ -49,7 +49,7 @@ impl Storage for FileStorage { async fn get_file_info(&self, file_id: &str) -> TuserResult<FileInfo> { let info_path = self.info_file_path(file_id); if !info_path.exists() { - return Err(TuserError::FileNotFound(String::from(file_id))); + return Err(TuserError::FileNotFound); } let contents = read_to_string(info_path).await.map_err(|err| { error!("{:?}", err); @@ -83,7 +83,10 @@ impl Storage for FileStorage { } async fn get_contents(&self, file_id: &str) -> TuserResult<NamedFile> { - Err(TuserError::FileNotFound(String::from(file_id))) + NamedFile::open(self.data_file_path(file_id)).map_err(|err| { + error!("{:?}", err); + TuserError::FileNotFound + }) } async fn add_bytes( @@ -162,11 +165,11 @@ impl Storage for FileStorage { async fn remove_file(&self, file_id: &str) -> TuserResult<()> { let info_path = self.info_file_path(file_id); if !info_path.exists() { - return Err(TuserError::FileNotFound(String::from(file_id))); + return Err(TuserError::FileNotFound); } let data_path = self.data_file_path(file_id); if !data_path.exists() { - return Err(TuserError::FileNotFound(String::from(file_id))); + return Err(TuserError::FileNotFound); } remove_file(info_path).await.map_err(|err| { error!("{:?}", err); diff --git a/src/storages/mod.rs b/src/storages/mod.rs index afeb42ace6b409b1d047a9b271c836a7cd019e09..ee35ba3963d43da13534665b5d3e214507d6ad4f 100644 --- a/src/storages/mod.rs +++ b/src/storages/mod.rs @@ -3,8 +3,8 @@ use std::str::FromStr; use actix_files::NamedFile; use async_trait::async_trait; -use chrono::{DateTime, Utc}; use chrono::serde::ts_seconds; +use chrono::{DateTime, Utc}; use derive_more::{Display, From}; use serde::{Deserialize, Serialize}; use sqlx::FromRow; diff --git a/src/storages/sqlite_file_storage.rs b/src/storages/sqlite_file_storage.rs index d0039dc514067fa1ab609b216d054409f534b9be..fcd401d3a56201868d83c36317eb10887a214da8 100644 --- a/src/storages/sqlite_file_storage.rs +++ b/src/storages/sqlite_file_storage.rs @@ -47,13 +47,23 @@ impl Storage for SQLiteFileStorage { .map_err(|err| TuserError::UnableToPrepareStorage(err.to_string()))?; } if !self.app_conf.storage_opts.sqlite_dsn.exists() { - File::create(self.app_conf.storage_opts.sqlite_dsn.clone()).await.map_err(|err| { - TuserError::UnableToPrepareStorage(err.to_string()) - })?; + File::create(self.app_conf.storage_opts.sqlite_dsn.clone()) + .await + .map_err(|err| TuserError::UnableToPrepareStorage(err.to_string()))?; } let pool = SqlitePoolOptions::new() .max_connections(10) - .connect(format!("sqlite://{}", self.app_conf.storage_opts.sqlite_dsn.as_display().to_string()).as_str()) + .connect( + format!( + "sqlite://{}", + self.app_conf + .storage_opts + .sqlite_dsn + .as_display() + .to_string() + ) + .as_str(), + ) .await .map_err(TuserError::from)?; sqlx::query( @@ -67,7 +77,9 @@ impl Storage for SQLiteFileStorage { deferred_size BOOLEAN, \ metadata TEXT\ );", - ).execute(&pool).await?; + ) + .execute(&pool) + .await?; self.pool = Some(pool); Ok(()) }