#![allow(clippy::redundant_pub_crate)]
#[cfg(feature = "async")]
use crate::v1::download_and_decrypt_file_async;
use crate::{
crypto, queries, utils,
v1::{
download_and_decrypt_file, download_file, response_payload, FileStorageInfo, FolderData, HasFileLocation,
HasFileMetadata, HasFiles, HasFolders, HasLinkedFileMetadata, HasLinkedLocationName, HasSharedFileMetadata,
HasSharedLocationName, HasUuid, ParentOrBase,
},
FilenSettings,
};
use secstr::SecUtf8;
use serde::{Deserialize, Serialize};
use snafu::{ResultExt, Snafu};
use uuid::Uuid;
type Result<T, E = Error> = std::result::Result<T, E>;
const DOWNLOAD_DIR_PATH: &str = "/v1/download/dir";
const DOWNLOAD_DIR_LINK_PATH: &str = "/v1/download/dir/link";
const DOWNLOAD_DIR_SHARED_PATH: &str = "/v1/download/dir/shared";
#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("Failed to decrypt file mime metadata '{}': {}", metadata, source))]
DecryptFileMimeMetadataFailed { metadata: String, source: crypto::Error },
#[snafu(display("Failed to decrypt file name metadata '{}': {}", metadata, source))]
DecryptFileNameMetadataFailed { metadata: String, source: crypto::Error },
#[snafu(display("Failed to decrypt file size metadata '{}': {}", metadata, source))]
DecryptFileSizeMetadataFailed { metadata: String, source: crypto::Error },
#[snafu(display("Decrypted size '{}' was invalid: {}", size, source))]
DecryptedSizeIsInvalid {
size: String,
source: std::num::ParseIntError,
},
#[snafu(display("Download and decrypt operation failed for linked file {}: {}", file_data, source))]
DownloadAndDecryptLinkedFileFailed {
file_data: Box<LinkedFileData>,
source: download_file::Error,
},
#[snafu(display("Download and decrypt operation failed for shared file {}: {}", file_data, source))]
DownloadAndDecryptSharedFileFailed {
file_data: Box<SharedFileData>,
source: download_file::Error,
},
#[snafu(display("{} query failed: {}", DOWNLOAD_DIR_LINK_PATH, source))]
DownloadDirLinkQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", DOWNLOAD_DIR_SHARED_PATH, source))]
DownloadDirSharedQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", DOWNLOAD_DIR_PATH, source))]
DownloadDirQueryFailed { source: queries::Error },
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct DownloadDirLinkRequestPayload<'download_dir_link> {
pub uuid: Uuid,
pub parent: Uuid,
pub password: &'download_dir_link str,
}
utils::display_from_json_with_lifetime!('download_dir_link, DownloadDirLinkRequestPayload);
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct LinkedFolderData {
pub uuid: Uuid,
#[serde(rename = "name")]
pub name_metadata: String,
pub parent: ParentOrBase,
}
utils::display_from_json!(LinkedFolderData);
impl HasLinkedLocationName for LinkedFolderData {
fn name_metadata_ref(&self) -> &str {
&self.name_metadata
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct LinkedFileData {
pub uuid: Uuid,
#[serde(flatten)]
pub storage: FileStorageInfo,
pub parent: Uuid,
pub metadata: String,
pub version: u32,
}
utils::display_from_json!(LinkedFileData);
impl HasLinkedFileMetadata for LinkedFileData {
fn file_metadata_ref(&self) -> &str {
&self.metadata
}
}
impl HasFileLocation for LinkedFileData {
fn file_storage_ref(&self) -> &FileStorageInfo {
&self.storage
}
}
impl HasUuid for LinkedFileData {
fn uuid_ref(&self) -> &Uuid {
&self.uuid
}
}
impl LinkedFileData {
gen_download_and_decrypt_file!();
}
macro_rules! gen_download_and_decrypt_file {
(
) => {
pub fn download_and_decrypt_file<W: std::io::Write>(
&self,
file_key: &secstr::SecUtf8,
writer: &mut std::io::BufWriter<W>,
settings: &crate::SettingsBundle,
) -> Result<u64, crate::v1::download_file::Error> {
download_and_decrypt_file(
&self.get_file_location(),
self.version,
file_key,
writer,
settings,
)
}
#[cfg(feature = "async")]
pub async fn download_and_decrypt_file_async<W: std::io::Write + Send>(
&self,
file_key: &secstr::SecUtf8,
writer: &mut std::io::BufWriter<W>,
settings: &crate::SettingsBundle,
) -> Result<u64, crate::v1::download_file::Error> {
download_and_decrypt_file_async(
&self.get_file_location(),
self.version,
file_key,
writer,
settings,
)
.await
}
};
}
pub(crate) use gen_download_and_decrypt_file;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct DownloadDirLinkResponseData {
pub folders: Vec<LinkedFolderData>,
pub files: Vec<LinkedFileData>,
}
utils::display_from_json!(DownloadDirLinkResponseData);
response_payload!(
DownloadDirLinkResponsePayload<DownloadDirLinkResponseData>
);
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct DownloadDirSharedRequestPayload<'download_dir_shared> {
#[serde(rename = "apiKey")]
pub api_key: &'download_dir_shared SecUtf8,
pub uuid: Uuid,
}
utils::display_from_json_with_lifetime!('download_dir_shared, DownloadDirSharedRequestPayload);
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct SharedFolderData {
pub uuid: Uuid,
#[serde(rename = "name")]
pub name_metadata: String,
pub parent: ParentOrBase,
}
utils::display_from_json!(SharedFolderData);
impl HasSharedLocationName for SharedFolderData {
fn name_metadata_ref(&self) -> &str {
&self.name_metadata
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct SharedFileData {
pub uuid: Uuid,
#[serde(flatten)]
pub storage: FileStorageInfo,
pub parent: Uuid,
pub metadata: String,
pub version: u32,
}
utils::display_from_json!(SharedFileData);
impl HasSharedFileMetadata for SharedFileData {
fn file_metadata_ref(&self) -> &str {
&self.metadata
}
}
impl HasFileLocation for SharedFileData {
fn file_storage_ref(&self) -> &FileStorageInfo {
&self.storage
}
}
impl HasUuid for SharedFileData {
fn uuid_ref(&self) -> &Uuid {
&self.uuid
}
}
impl SharedFileData {
gen_download_and_decrypt_file!();
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct DownloadDirSharedResponseData {
pub folders: Vec<SharedFolderData>,
pub files: Vec<SharedFileData>,
}
utils::display_from_json!(DownloadDirSharedResponseData);
response_payload!(
DownloadDirSharedResponsePayload<DownloadDirSharedResponseData>
);
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct DownloadDirRequestPayload<'download_dir> {
#[serde(rename = "apiKey")]
pub api_key: &'download_dir SecUtf8,
pub uuid: Uuid,
}
utils::display_from_json_with_lifetime!('download_dir, DownloadDirRequestPayload);
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct DownloadDirResponseData {
pub folders: Vec<FolderData>,
pub files: Vec<FileData>,
}
utils::display_from_json!(DownloadDirResponseData);
impl HasFiles<FileData> for DownloadDirResponseData {
fn files_ref(&self) -> &[FileData] {
&self.files
}
}
impl HasFolders<FolderData> for DownloadDirResponseData {
fn folders_ref(&self) -> &[FolderData] {
&self.folders
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct FileData {
pub uuid: Uuid,
#[serde(flatten)]
pub storage: FileStorageInfo,
#[serde(rename = "name")]
pub name_metadata: String,
#[serde(rename = "size")]
pub size_metadata: String,
#[serde(rename = "mime")]
pub mime_metadata: String,
pub parent: Uuid,
pub metadata: String,
pub version: u32,
}
utils::display_from_json!(FileData);
impl HasFileMetadata for FileData {
fn file_metadata_ref(&self) -> &str {
&self.metadata
}
}
impl HasFileLocation for FileData {
fn file_storage_ref(&self) -> &FileStorageInfo {
&self.storage
}
}
impl HasUuid for FileData {
fn uuid_ref(&self) -> &Uuid {
&self.uuid
}
}
impl FileData {
pub fn decrypt_name_size_mime(&self, file_key: &SecUtf8) -> Result<FileNameSizeMime> {
let name = crypto::decrypt_metadata_str(&self.name_metadata, file_key).context(
DecryptFileNameMetadataFailedSnafu {
metadata: self.name_metadata.clone(),
},
)?;
let size_string = &crypto::decrypt_metadata_str(&self.size_metadata, file_key).context(
DecryptFileSizeMetadataFailedSnafu {
metadata: self.size_metadata.clone(),
},
)?;
let size = str::parse::<u64>(size_string).context(DecryptedSizeIsInvalidSnafu { size: size_string })?;
let mime = crypto::decrypt_metadata_str(&self.mime_metadata, file_key).context(
DecryptFileMimeMetadataFailedSnafu {
metadata: self.mime_metadata.clone(),
},
)?;
Ok(FileNameSizeMime { name, size, mime })
}
gen_download_and_decrypt_file!();
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct FileNameSizeMime {
pub name: String,
pub size: u64,
pub mime: String,
}
utils::display_from_json!(FileNameSizeMime);
response_payload!(
DownloadDirResponsePayload<DownloadDirResponseData>
);
pub fn download_dir_link_request(
payload: &DownloadDirLinkRequestPayload,
filen_settings: &FilenSettings,
) -> Result<DownloadDirLinkResponsePayload> {
queries::query_filen_api(DOWNLOAD_DIR_LINK_PATH, payload, filen_settings)
.context(DownloadDirLinkQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn download_dir_link_request_async(
payload: &DownloadDirLinkRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<DownloadDirLinkResponsePayload> {
queries::query_filen_api_async(DOWNLOAD_DIR_LINK_PATH, payload, filen_settings)
.await
.context(DownloadDirLinkQueryFailedSnafu {})
}
pub fn download_dir_shared_request(
payload: &DownloadDirSharedRequestPayload,
filen_settings: &FilenSettings,
) -> Result<DownloadDirSharedResponsePayload> {
queries::query_filen_api(DOWNLOAD_DIR_SHARED_PATH, payload, filen_settings)
.context(DownloadDirSharedQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn download_dir_shared_request_async(
payload: &DownloadDirSharedRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<DownloadDirSharedResponsePayload> {
queries::query_filen_api_async(DOWNLOAD_DIR_SHARED_PATH, payload, filen_settings)
.await
.context(DownloadDirSharedQueryFailedSnafu {})
}
pub fn download_dir_request(
payload: &DownloadDirRequestPayload,
filen_settings: &FilenSettings,
) -> Result<DownloadDirResponsePayload> {
queries::query_filen_api(DOWNLOAD_DIR_PATH, payload, filen_settings).context(DownloadDirQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn download_dir_request_async(
payload: &DownloadDirRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<DownloadDirResponsePayload> {
queries::query_filen_api_async(DOWNLOAD_DIR_PATH, payload, filen_settings)
.await
.context(DownloadDirQueryFailedSnafu {})
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "async")]
use crate::test_utils::validate_contract_async;
use crate::test_utils::{deserialize_from_file, validate_contract};
use once_cell::sync::Lazy;
use secstr::SecUtf8;
static API_KEY: Lazy<SecUtf8> =
Lazy::new(|| SecUtf8::from("bYZmrwdVEbHJSqeA0RfnPtKiBcXzUpRdKGRkjw9m1o1eqSGP1s6DM10CDnklpFq6"));
#[test]
fn download_dir_response_data_file_should_be_correctly_decrypted() {
let m_key = SecUtf8::from("ed8d39b6c2d00ece398199a3e83988f1c4942b24");
let download_dir_response: DownloadDirResponsePayload =
deserialize_from_file("tests/resources/responses/download_dir.json");
let data = download_dir_response.data.unwrap();
let test_file = &data.files[0];
let test_file_metadata_result = test_file.decrypt_file_metadata(&[m_key]);
let test_file_metadata = test_file_metadata_result.unwrap();
assert_eq!(test_file_metadata.key.unsecure(), "sh1YRHfx22Ij40tQBbt6BgpBlqkzch8Y");
assert_eq!(test_file_metadata.last_modified, 1_383_742_218);
assert_eq!(test_file_metadata.mime, "image/png");
assert_eq!(test_file_metadata.name, "lina.png");
assert_eq!(test_file_metadata.size, 133_641);
let test_file_name_size_mime_result = test_file.decrypt_name_size_mime(&test_file_metadata.key);
let test_file_name_size_mime = test_file_name_size_mime_result.unwrap();
assert_eq!(test_file_name_size_mime.mime, test_file_metadata.mime);
assert_eq!(test_file_name_size_mime.name, test_file_metadata.name);
assert_eq!(test_file_name_size_mime.size, test_file_metadata.size);
}
#[test]
fn download_dir_request_should_be_correctly_typed() {
let request_payload = DownloadDirRequestPayload {
api_key: &API_KEY,
uuid: Uuid::parse_str("cf2af9a0-6f4e-485d-862c-0459f4662cf1").unwrap(),
};
validate_contract(
DOWNLOAD_DIR_PATH,
request_payload,
"tests/resources/responses/download_dir.json",
|request_payload, filen_settings| download_dir_request(&request_payload, &filen_settings),
);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn download_dir_request_async_should_be_correctly_typed() {
let request_payload = DownloadDirRequestPayload {
api_key: &API_KEY,
uuid: Uuid::parse_str("cf2af9a0-6f4e-485d-862c-0459f4662cf1").unwrap(),
};
validate_contract_async(
DOWNLOAD_DIR_PATH,
request_payload,
"tests/resources/responses/download_dir.json",
|request_payload, filen_settings| async move {
download_dir_request_async(&request_payload, &filen_settings).await
},
)
.await;
}
#[test]
fn download_dir_link_request_should_be_correctly_typed() {
let request_payload = DownloadDirLinkRequestPayload {
uuid: Uuid::parse_str("5c86494b-36ec-4d39-a839-9f391474ad00").unwrap(),
parent: Uuid::parse_str("b013e93f-4c9b-4df3-a6de-093d95f13c57").unwrap(),
password: "4366faac2229d73a206dcc4384e4a560be054f69d8e9ecc307d7d1701c90b3d59/
dd56676f7593a464d72755501462287393cc91a6c575eade9fa50ecafd4142d",
};
validate_contract(
DOWNLOAD_DIR_LINK_PATH,
request_payload,
"tests/resources/responses/download_dir_link.json",
|request_payload, filen_settings| download_dir_link_request(&request_payload, &filen_settings),
);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn download_dir_link_request_async_should_be_correctly_typed() {
let request_payload = DownloadDirLinkRequestPayload {
uuid: Uuid::parse_str("5c86494b-36ec-4d39-a839-9f391474ad00").unwrap(),
parent: Uuid::parse_str("b013e93f-4c9b-4df3-a6de-093d95f13c57").unwrap(),
password: "4366faac2229d73a206dcc4384e4a560be054f69d8e9ecc307d7d1701c90b3d59/
dd56676f7593a464d72755501462287393cc91a6c575eade9fa50ecafd4142d",
};
validate_contract_async(
DOWNLOAD_DIR_LINK_PATH,
request_payload,
"tests/resources/responses/download_dir_link.json",
|request_payload, filen_settings| async move {
download_dir_link_request_async(&request_payload, &filen_settings).await
},
)
.await;
}
#[test]
fn download_dir_shared_request_should_be_correctly_typed() {
let request_payload = DownloadDirSharedRequestPayload {
api_key: &API_KEY,
uuid: Uuid::parse_str("5c86494b-36ec-4d39-a839-9f391474ad00").unwrap(),
};
validate_contract(
DOWNLOAD_DIR_SHARED_PATH,
request_payload,
"tests/resources/responses/download_dir_shared.json",
|request_payload, filen_settings| download_dir_shared_request(&request_payload, &filen_settings),
);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn download_dir_shared_request_async_should_be_correctly_typed() {
let request_payload = DownloadDirSharedRequestPayload {
api_key: &API_KEY,
uuid: Uuid::parse_str("5c86494b-36ec-4d39-a839-9f391474ad00").unwrap(),
};
validate_contract_async(
DOWNLOAD_DIR_SHARED_PATH,
request_payload,
"tests/resources/responses/download_dir_shared.json",
|request_payload, filen_settings| async move {
download_dir_shared_request_async(&request_payload, &filen_settings).await
},
)
.await;
}
}