From ab94be2361689265f986a49babf868fe3b4c1d60 Mon Sep 17 00:00:00 2001 From: Fabian Schmidt Date: Sat, 24 Dec 2022 15:10:00 +0100 Subject: [PATCH 1/3] Allow downloading files Co-authored-by: Danilo Bargen --- README.md | 2 +- src/api.rs | 14 +++++++++++++- src/connection.rs | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4458f3caa..02961d3fb 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ client library in Rust. For implementation status, see feature list below. **Files** - [x] Upload files -- [ ] Download files +- [x] Download files ## Usage diff --git a/src/api.rs b/src/api.rs index acb6b455f..0f7cd278c 100644 --- a/src/api.rs +++ b/src/api.rs @@ -9,7 +9,7 @@ use reqwest::Client; use sodiumoxide::crypto::box_::PublicKey; use crate::{ - connection::{blob_upload, send_e2e, send_simple, Recipient}, + connection::{blob_download, blob_upload, send_e2e, send_simple, Recipient}, crypto::{ encrypt, encrypt_file_msg, encrypt_image_msg, encrypt_raw, EncryptedMessage, RecipientKey, }, @@ -379,6 +379,18 @@ impl E2eApi { .await } + /// Download a blob from the blob server and return the bytes. + pub async fn blob_download(&self, blob_id: &BlobId) -> Result, ApiError> { + blob_download( + &self.client, + self.endpoint.borrow(), + &self.id, + &self.secret, + blob_id, + ) + .await + } + /// Deserialize an incoming Threema Gateway message in /// `application/x-www-form-urlencoded` format. /// diff --git a/src/connection.rs b/src/connection.rs index da7f6a5e2..7da2826e9 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -197,6 +197,24 @@ pub(crate) async fn blob_upload( BlobId::from_str(res.text().await?.trim()) } +/// Download a blob from the blob server. +pub(crate) async fn blob_download( + client: &Client, + endpoint: &str, + from: &str, + secret: &str, + blob_id: &BlobId, +) -> Result, ApiError> { + // Build URL + let url = format!( + "{}/blobs/{}?from={}&secret={}", + endpoint, blob_id, from, secret + ); + + // Send request + Ok(client.get(&url).send().await?.bytes().await?.to_vec()) +} + #[cfg(test)] mod tests { use super::*; From 7699841ad54f59198ce4c7e9b9a65e4e6ce8f98b Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Sat, 24 Dec 2022 15:44:00 +0100 Subject: [PATCH 2/3] Check server response code after blob download --- src/connection.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/connection.rs b/src/connection.rs index 7da2826e9..6db5b0d72 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -212,7 +212,11 @@ pub(crate) async fn blob_download( ); // Send request - Ok(client.get(&url).send().await?.bytes().await?.to_vec()) + let res = client.get(&url).send().await?; + map_response_code(res.status(), Some(ApiError::BadBlob))?; + + // Read response bytes + Ok(res.bytes().await?.to_vec()) } #[cfg(test)] From 6b93eadfeeac7e40d6043065922123cfe62985b4 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Sat, 24 Dec 2022 15:51:53 +0100 Subject: [PATCH 3/3] Add download_blob example --- CHANGELOG.md | 1 + README.md | 4 ++++ examples/download_blob.rs | 50 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 examples/download_blob.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e83606ce6..6d5faf37a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Possible log types: ### Unreleased +- [added] Support downloading of blobs (#65) - [changed] Remove `mime` dependency in favor of plain strings (#64) ### v0.15.1 (2021-12-06) diff --git a/README.md b/README.md index 02961d3fb..5b677ece9 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,10 @@ Decode and decrypt an incoming message payload: cargo run --example receive -- +Download a blob: + + cargo run --example download_blob -- + ## Cargo Features diff --git a/examples/download_blob.rs b/examples/download_blob.rs new file mode 100644 index 000000000..f6db77c04 --- /dev/null +++ b/examples/download_blob.rs @@ -0,0 +1,50 @@ +use std::process; + +use data_encoding::HEXLOWER_PERMISSIVE; +use docopt::Docopt; +use threema_gateway::{ApiBuilder, BlobId}; + +const USAGE: &str = " +Usage: download_blob [options] + +Options: + -h, --help Show this help +"; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + let args = Docopt::new(USAGE) + .and_then(|docopt| docopt.parse()) + .unwrap_or_else(|e| e.exit()); + + // Command line arguments + let our_id = args.get_str(""); + let secret = args.get_str(""); + let private_key = args.get_str(""); + let blob_id: BlobId = match args.get_str("").parse() { + Ok(val) => val, + Err(e) => { + eprintln!("Could not decode blob ID from hex: {}", e); + process::exit(1); + } + }; + + // Create E2eApi instance + let api = ApiBuilder::new(our_id, secret) + .with_private_key_str(private_key) + .and_then(|builder| builder.into_e2e()) + .unwrap(); + + // Download blob + println!("Downloading blob with ID {}...", blob_id); + match api.blob_download(&blob_id).await { + Err(e) => { + eprintln!("Could not download blob: {}", e); + process::exit(1); + } + Ok(bytes) => { + println!("Downloaded {} blob bytes:", bytes.len()); + println!("{}", HEXLOWER_PERMISSIVE.encode(&bytes)); + } + } +}