Skip to content

Commit

Permalink
Merge pull request #65 from dbrgn/blob-download
Browse files Browse the repository at this point in the history
Implement blob downloading
  • Loading branch information
dbrgn authored Dec 24, 2022
2 parents 4dd5588 + 6b93ead commit d43f313
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -83,6 +83,10 @@ Decode and decrypt an incoming message payload:

cargo run --example receive -- <our-id> <secret> <private-key> <request-body>

Download a blob:

cargo run --example download_blob -- <our-id> <secret> <private-key> <blob-id>


## Cargo Features

Expand Down
50 changes: 50 additions & 0 deletions examples/download_blob.rs
Original file line number Diff line number Diff line change
@@ -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] <our-id> <secret> <private-key> <blob-id>
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("<our-id>");
let secret = args.get_str("<secret>");
let private_key = args.get_str("<private-key>");
let blob_id: BlobId = match args.get_str("<blob-id>").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));
}
}
}
14 changes: 13 additions & 1 deletion src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down Expand Up @@ -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<Vec<u8>, 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.
///
Expand Down
22 changes: 22 additions & 0 deletions src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,28 @@ 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<Vec<u8>, ApiError> {
// Build URL
let url = format!(
"{}/blobs/{}?from={}&secret={}",
endpoint, blob_id, from, secret
);

// Send request
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)]
mod tests {
use super::*;
Expand Down

0 comments on commit d43f313

Please sign in to comment.