mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-03 17:49:15 +02:00
Extract methods in API download logic
This commit is contained in:
parent
d382a015c5
commit
268da48d9c
1 changed files with 142 additions and 199 deletions
|
@ -1,11 +1,15 @@
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io::{
|
||||||
|
self,
|
||||||
|
Read,
|
||||||
|
};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use openssl::symm::decrypt_aead;
|
use openssl::symm::decrypt_aead;
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
Client,
|
Client,
|
||||||
Error as ReqwestError,
|
Error as ReqwestError,
|
||||||
|
Response,
|
||||||
};
|
};
|
||||||
use reqwest::header::Authorization;
|
use reqwest::header::Authorization;
|
||||||
use reqwest::header::ContentLength;
|
use reqwest::header::ContentLength;
|
||||||
|
@ -48,20 +52,39 @@ impl<'a> Download<'a> {
|
||||||
// Create a key set for the file
|
// Create a key set for the file
|
||||||
let mut key = KeySet::from(self.file);
|
let mut key = KeySet::from(self.file);
|
||||||
|
|
||||||
// Build the meta cipher
|
// Fetch the authentication nonce
|
||||||
// let mut metadata_tag = vec![0u8; 16];
|
let auth_nonce = self.fetch_auth_nonce(client);
|
||||||
// let mut meta_cipher = match encrypt_aead(
|
|
||||||
// KeySet::cipher(),
|
|
||||||
// self.meta_key().unwrap(),
|
|
||||||
// self.iv,
|
|
||||||
// &[],
|
|
||||||
// &metadata,
|
|
||||||
// &mut metadata_tag,
|
|
||||||
// ) {
|
|
||||||
// Ok(cipher) => cipher,
|
|
||||||
// Err(_) => // TODO: return error here,
|
|
||||||
// };
|
|
||||||
|
|
||||||
|
// Fetch the meta nonce, set the input vector
|
||||||
|
let meta_nonce = self.fetch_meta_nonce(&client, &mut key, auth_nonce);
|
||||||
|
|
||||||
|
// Open the file we will write to
|
||||||
|
// TODO: this should become a temporary file first
|
||||||
|
let out = File::create("downloaded.zip")
|
||||||
|
.expect("failed to open file");
|
||||||
|
|
||||||
|
// Create the file reader for downloading
|
||||||
|
let (reader, len) = self.create_file_reader(&key, meta_nonce, &client);
|
||||||
|
|
||||||
|
// Create the file writer
|
||||||
|
let writer = self.create_file_writer(
|
||||||
|
out,
|
||||||
|
len,
|
||||||
|
&key,
|
||||||
|
reporter.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Download the file
|
||||||
|
self.download(reader, writer, len, reporter);
|
||||||
|
|
||||||
|
// TODO: return the file path
|
||||||
|
// TODO: return the new remote state (does it still exist remote)
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the authentication nonce for the file from the Send server.
|
||||||
|
fn fetch_auth_nonce(&self, client: &Client) -> Vec<u8> {
|
||||||
// Get the download url, and parse the nonce
|
// Get the download url, and parse the nonce
|
||||||
// TODO: do not unwrap here, return error
|
// TODO: do not unwrap here, return error
|
||||||
let download_url = self.file.download_url(false);
|
let download_url = self.file.download_url(false);
|
||||||
|
@ -78,7 +101,7 @@ impl<'a> Download<'a> {
|
||||||
|
|
||||||
// Get the authentication nonce
|
// Get the authentication nonce
|
||||||
// TODO: don't unwrap here, return an error
|
// TODO: don't unwrap here, return an error
|
||||||
let nonce = b64::decode(
|
b64::decode(
|
||||||
response.headers()
|
response.headers()
|
||||||
.get_raw(HEADER_AUTH_NONCE)
|
.get_raw(HEADER_AUTH_NONCE)
|
||||||
.expect("missing authenticate header")
|
.expect("missing authenticate header")
|
||||||
|
@ -91,17 +114,49 @@ impl<'a> Download<'a> {
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.next()
|
.next()
|
||||||
.expect("missing authentication nonce")
|
.expect("missing authentication nonce")
|
||||||
).expect("failed to decode authentication nonce");
|
).expect("failed to decode authentication nonce")
|
||||||
|
}
|
||||||
|
|
||||||
// Compute the cryptographic signature
|
/// Fetch the metadata nonce.
|
||||||
|
/// This method also sets the input vector on the given key set,
|
||||||
|
/// extracted from the metadata.
|
||||||
|
///
|
||||||
|
/// The key set, along with the authentication nonce must be given.
|
||||||
|
/// The meta nonce is returned.
|
||||||
|
fn fetch_meta_nonce(
|
||||||
|
&self,
|
||||||
|
client: &Client,
|
||||||
|
key: &mut KeySet,
|
||||||
|
auth_nonce: Vec<u8>,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
// Fetch the metadata and the nonce
|
||||||
|
let (metadata, meta_nonce) = self.fetch_metadata(client, key, auth_nonce);
|
||||||
|
|
||||||
|
// Set the input vector, and return the nonce
|
||||||
|
key.set_iv(metadata.iv());
|
||||||
|
meta_nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a metadata nonce, and fetch the metadata for the file from the
|
||||||
|
/// Send server.
|
||||||
|
///
|
||||||
|
/// The key set, along with the authentication nonce must be given.
|
||||||
|
///
|
||||||
|
/// The metadata, with the meta nonce is returned.
|
||||||
|
fn fetch_metadata(
|
||||||
|
&self,
|
||||||
|
client: &Client,
|
||||||
|
key: &KeySet,
|
||||||
|
auth_nonce: Vec<u8>,
|
||||||
|
) -> (Metadata, Vec<u8>) {
|
||||||
|
// Compute the cryptographic signature for authentication
|
||||||
// TODO: do not unwrap, return an error
|
// TODO: do not unwrap, return an error
|
||||||
let sig = signature_encoded(key.auth_key().unwrap(), &nonce)
|
let sig = signature_encoded(key.auth_key().unwrap(), &auth_nonce)
|
||||||
.expect("failed to compute metadata signature");
|
.expect("failed to compute metadata signature");
|
||||||
|
|
||||||
// Get the meta URL, fetch the metadata
|
// Buidl the request, fetch the encrypted metadata
|
||||||
// TODO: do not unwrap here, return error
|
// TODO: do not unwrap here, return error
|
||||||
let meta_url = self.file.api_meta_url();
|
let mut response = client.get(self.file.api_meta_url())
|
||||||
let mut response = client.get(meta_url)
|
|
||||||
.header(Authorization(
|
.header(Authorization(
|
||||||
format!("send-v1 {}", sig)
|
format!("send-v1 {}", sig)
|
||||||
))
|
))
|
||||||
|
@ -132,24 +187,36 @@ impl<'a> Download<'a> {
|
||||||
.expect("missing metadata nonce")
|
.expect("missing metadata nonce")
|
||||||
).expect("failed to decode metadata nonce");
|
).expect("failed to decode metadata nonce");
|
||||||
|
|
||||||
// Parse the metadata response
|
// Parse the metadata response, and decrypt it
|
||||||
let meta_response: MetadataResponse = response.json()
|
(
|
||||||
.expect("failed to parse metadata response");
|
response.json::<MetadataResponse>()
|
||||||
|
.expect("failed to parse metadata response")
|
||||||
// Decrypt the metadata, set the input vector
|
.decrypt_metadata(&key)
|
||||||
let metadata = meta_response.decrypt_metadata(&key)
|
.expect("failed to decrypt metadata"),
|
||||||
.expect("failed to decrypt metadata");
|
nonce,
|
||||||
key.set_iv(metadata.iv());
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make a download request, and create a reader that downloads the
|
||||||
|
/// encrypted file.
|
||||||
|
///
|
||||||
|
/// The response representing the file reader is returned along with the
|
||||||
|
/// length of the reader content.
|
||||||
|
fn create_file_reader(
|
||||||
|
&self,
|
||||||
|
key: &KeySet,
|
||||||
|
meta_nonce: Vec<u8>,
|
||||||
|
client: &Client,
|
||||||
|
) -> (Response, u64) {
|
||||||
// Compute the cryptographic signature
|
// Compute the cryptographic signature
|
||||||
|
// TODO: use the metadata nonce here?
|
||||||
// TODO: do not unwrap, return an error
|
// TODO: do not unwrap, return an error
|
||||||
let sig = signature_encoded(key.auth_key().unwrap(), &nonce)
|
let sig = signature_encoded(key.auth_key().unwrap(), &meta_nonce)
|
||||||
.expect("failed to compute file signature");
|
.expect("failed to compute file signature");
|
||||||
|
|
||||||
// Get the download URL, build the download request
|
// Build and send the download request
|
||||||
// TODO: do not unwrap here, return error
|
// TODO: do not unwrap here, return error
|
||||||
let download_url = self.file.api_download_url();
|
let response = client.get(self.file.api_download_url())
|
||||||
let mut response = client.get(download_url)
|
|
||||||
.header(Authorization(
|
.header(Authorization(
|
||||||
format!("send-v1 {}", sig)
|
format!("send-v1 {}", sig)
|
||||||
))
|
))
|
||||||
|
@ -164,32 +231,59 @@ impl<'a> Download<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the content length
|
// Get the content length
|
||||||
let response_len = response.headers().get::<ContentLength>()
|
// TODO: make sure there is enough disk space
|
||||||
|
let len = response.headers().get::<ContentLength>()
|
||||||
.expect("failed to fetch file, missing content length header")
|
.expect("failed to fetch file, missing content length header")
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
// Open a file to write to, and build an encrypted writer
|
(response, len)
|
||||||
// TODO: this should become a temporary file first
|
}
|
||||||
let out = File::create("downloaded.zip")
|
|
||||||
.expect("failed to open file");
|
/// Create a file writer.
|
||||||
let writer = EncryptedFileWriter::new(
|
///
|
||||||
out,
|
/// This writer will will decrypt the input on the fly, and writes the
|
||||||
response_len as usize,
|
/// decrypted data to the given file.
|
||||||
|
fn create_file_writer(
|
||||||
|
&self,
|
||||||
|
file: File,
|
||||||
|
len: u64,
|
||||||
|
key: &KeySet,
|
||||||
|
reporter: Arc<Mutex<ProgressReporter>>,
|
||||||
|
) -> ProgressWriter<EncryptedFileWriter> {
|
||||||
|
// Build an encrypted writer
|
||||||
|
let mut writer = ProgressWriter::new(
|
||||||
|
EncryptedFileWriter::new(
|
||||||
|
file,
|
||||||
|
len as usize,
|
||||||
KeySet::cipher(),
|
KeySet::cipher(),
|
||||||
key.file_key().unwrap(),
|
key.file_key().unwrap(),
|
||||||
key.iv(),
|
key.iv(),
|
||||||
|
).expect("failed to create encrypted writer")
|
||||||
).expect("failed to create encrypted writer");
|
).expect("failed to create encrypted writer");
|
||||||
let mut writer = ProgressWriter::new(writer)
|
|
||||||
.expect("failed to create encrypted writer");
|
// Set the reporter
|
||||||
writer.set_reporter(reporter.clone());
|
writer.set_reporter(reporter.clone());
|
||||||
|
|
||||||
|
writer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Download the file from the reader, and write it to the writer.
|
||||||
|
/// The length of the file must also be given.
|
||||||
|
/// The status will be reported to the given progress reporter.
|
||||||
|
fn download<R: Read>(
|
||||||
|
&self,
|
||||||
|
mut reader: R,
|
||||||
|
mut writer: ProgressWriter<EncryptedFileWriter>,
|
||||||
|
len: u64,
|
||||||
|
reporter: Arc<Mutex<ProgressReporter>>,
|
||||||
|
) {
|
||||||
// Start the writer
|
// Start the writer
|
||||||
reporter.lock()
|
reporter.lock()
|
||||||
.expect("unable to start progress, failed to get lock")
|
.expect("unable to start progress, failed to get lock")
|
||||||
.start(response_len);
|
.start(len);
|
||||||
|
|
||||||
// Write to the output file
|
// Write to the output file
|
||||||
io::copy(&mut response, &mut writer)
|
io::copy(&mut reader, &mut writer)
|
||||||
.expect("failed to download and decrypt file");
|
.expect("failed to download and decrypt file");
|
||||||
|
|
||||||
// Finish
|
// Finish
|
||||||
|
@ -200,158 +294,7 @@ impl<'a> Download<'a> {
|
||||||
// Verify the writer
|
// Verify the writer
|
||||||
// TODO: delete the file if verification failed, show a proper error
|
// TODO: delete the file if verification failed, show a proper error
|
||||||
assert!(writer.unwrap().verified(), "downloaded and decrypted file could not be verified");
|
assert!(writer.unwrap().verified(), "downloaded and decrypted file could not be verified");
|
||||||
|
|
||||||
// // Crpate metadata and a file reader
|
|
||||||
// let metadata = self.create_metadata(&key, &file)?;
|
|
||||||
// let reader = self.create_reader(&key, reporter.clone())?;
|
|
||||||
// let reader_len = reader.len().unwrap();
|
|
||||||
|
|
||||||
// // Create the request to send
|
|
||||||
// let req = self.create_request(
|
|
||||||
// client,
|
|
||||||
// &key,
|
|
||||||
// metadata,
|
|
||||||
// reader,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// // Start the reporter
|
|
||||||
// reporter.lock()
|
|
||||||
// .expect("unable to start progress, failed to get lock")
|
|
||||||
// .start(reader_len);
|
|
||||||
|
|
||||||
// // Execute the request
|
|
||||||
// let result = self.execute_request(req, client, &key);
|
|
||||||
|
|
||||||
// // Mark the reporter as finished
|
|
||||||
// reporter.lock()
|
|
||||||
// .expect("unable to finish progress, failed to get lock")
|
|
||||||
// .finish();
|
|
||||||
|
|
||||||
// TODO: return the file path
|
|
||||||
// TODO: return the new remote state (does it still exist remote)
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// Create a blob of encrypted metadata.
|
|
||||||
// fn create_metadata(&self, key: &KeySet, file: &FileData)
|
|
||||||
// -> Result<Vec<u8>>
|
|
||||||
// {
|
|
||||||
// // Construct the metadata
|
|
||||||
// let metadata = Metadata::from(
|
|
||||||
// key.iv(),
|
|
||||||
// file.name().to_owned(),
|
|
||||||
// file.mime().clone(),
|
|
||||||
// ).to_json().into_bytes();
|
|
||||||
|
|
||||||
// // Encrypt the metadata
|
|
||||||
// let mut metadata_tag = vec![0u8; 16];
|
|
||||||
// let mut metadata = match encrypt_aead(
|
|
||||||
// KeySet::cipher(),
|
|
||||||
// key.meta_key().unwrap(),
|
|
||||||
// Some(&[0u8; 12]),
|
|
||||||
// &[],
|
|
||||||
// &metadata,
|
|
||||||
// &mut metadata_tag,
|
|
||||||
// ) {
|
|
||||||
// Ok(metadata) => metadata,
|
|
||||||
// Err(_) => return Err(DownloadError::EncryptionError),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Append the encryption tag
|
|
||||||
// metadata.append(&mut metadata_tag);
|
|
||||||
|
|
||||||
// Ok(metadata)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Create a reader that reads the file as encrypted stream.
|
|
||||||
// fn create_reader(
|
|
||||||
// &self,
|
|
||||||
// key: &KeySet,
|
|
||||||
// reporter: Arc<Mutex<ProgressReporter>>,
|
|
||||||
// ) -> Result<EncryptedReader> {
|
|
||||||
// // Open the file
|
|
||||||
// let file = match File::open(self.path.as_path()) {
|
|
||||||
// Ok(file) => file,
|
|
||||||
// Err(_) => return Err(DownloadError::FileError),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Create an encrypted reader
|
|
||||||
// let reader = match EncryptedFileReaderTagged::new(
|
|
||||||
// file,
|
|
||||||
// KeySet::cipher(),
|
|
||||||
// key.file_key().unwrap(),
|
|
||||||
// key.iv(),
|
|
||||||
// ) {
|
|
||||||
// Ok(reader) => reader,
|
|
||||||
// Err(_) => return Err(DownloadError::EncryptionError),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Buffer the encrypted reader
|
|
||||||
// let reader = BufReader::new(reader);
|
|
||||||
|
|
||||||
// // Wrap into the encrypted reader
|
|
||||||
// let mut reader = ProgressReader::new(reader)
|
|
||||||
// .expect("failed to create progress reader");
|
|
||||||
|
|
||||||
// // Initialize and attach the reporter
|
|
||||||
// reader.set_reporter(reporter);
|
|
||||||
|
|
||||||
// Ok(reader)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Build the request that will be send to the server.
|
|
||||||
// fn create_request(
|
|
||||||
// &self,
|
|
||||||
// client: &Client,
|
|
||||||
// key: &KeySet,
|
|
||||||
// metadata: Vec<u8>,
|
|
||||||
// reader: EncryptedReader,
|
|
||||||
// ) -> Request {
|
|
||||||
// // Get the reader length
|
|
||||||
// let len = reader.len().expect("failed to get reader length");
|
|
||||||
|
|
||||||
// // Configure a form to send
|
|
||||||
// let part = Part::reader_with_length(reader, len)
|
|
||||||
// // .file_name(file.name())
|
|
||||||
// .mime(APPLICATION_OCTET_STREAM);
|
|
||||||
// let form = Form::new()
|
|
||||||
// .part("data", part);
|
|
||||||
|
|
||||||
// // Define the URL to call
|
|
||||||
// let url = self.host.join("api/upload").expect("invalid host");
|
|
||||||
|
|
||||||
// // Build the request
|
|
||||||
// client.post(url.as_str())
|
|
||||||
// .header(Authorization(
|
|
||||||
// format!("send-v1 {}", key.auth_key_encoded().unwrap())
|
|
||||||
// ))
|
|
||||||
// .header(XFileMetadata::from(&metadata))
|
|
||||||
// .multipart(form)
|
|
||||||
// .build()
|
|
||||||
// .expect("failed to build an API request")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Execute the given request, and create a file object that represents the
|
|
||||||
// /// uploaded file.
|
|
||||||
// fn execute_request(&self, req: Request, client: &Client, key: &KeySet)
|
|
||||||
// -> Result<SendFile>
|
|
||||||
// {
|
|
||||||
// // Execute the request
|
|
||||||
// let mut res = match client.execute(req) {
|
|
||||||
// Ok(res) => res,
|
|
||||||
// Err(err) => return Err(DownloadError::RequestError(err)),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Decode the response
|
|
||||||
// let res: DownloadResponse = match res.json() {
|
|
||||||
// Ok(res) => res,
|
|
||||||
// Err(_) => return Err(DownloadError::DecodeError),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Transform the responce into a file object
|
|
||||||
// Ok(res.into_file(self.host.clone(), &key))
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors that may occur in the upload action.
|
/// Errors that may occur in the upload action.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue