Extract methods in API download logic

This commit is contained in:
timvisee 2018-03-23 01:17:59 +01:00
parent d382a015c5
commit 268da48d9c
No known key found for this signature in database
GPG key ID: 109CBA0BF74036C2

View file

@ -1,11 +1,15 @@
use std::fs::File;
use std::io;
use std::io::{
self,
Read,
};
use std::sync::{Arc, Mutex};
use openssl::symm::decrypt_aead;
use reqwest::{
Client,
Error as ReqwestError,
Response,
};
use reqwest::header::Authorization;
use reqwest::header::ContentLength;
@ -48,20 +52,39 @@ impl<'a> Download<'a> {
// Create a key set for the file
let mut key = KeySet::from(self.file);
// Build the meta cipher
// let mut metadata_tag = vec![0u8; 16];
// 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 authentication nonce
let auth_nonce = self.fetch_auth_nonce(client);
// 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
// TODO: do not unwrap here, return error
let download_url = self.file.download_url(false);
@ -78,7 +101,7 @@ impl<'a> Download<'a> {
// Get the authentication nonce
// TODO: don't unwrap here, return an error
let nonce = b64::decode(
b64::decode(
response.headers()
.get_raw(HEADER_AUTH_NONCE)
.expect("missing authenticate header")
@ -91,17 +114,49 @@ impl<'a> Download<'a> {
.skip(1)
.next()
.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
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");
// Get the meta URL, fetch the metadata
// Buidl the request, fetch the encrypted metadata
// TODO: do not unwrap here, return error
let meta_url = self.file.api_meta_url();
let mut response = client.get(meta_url)
let mut response = client.get(self.file.api_meta_url())
.header(Authorization(
format!("send-v1 {}", sig)
))
@ -132,24 +187,36 @@ impl<'a> Download<'a> {
.expect("missing metadata nonce")
).expect("failed to decode metadata nonce");
// Parse the metadata response
let meta_response: MetadataResponse = response.json()
.expect("failed to parse metadata response");
// Decrypt the metadata, set the input vector
let metadata = meta_response.decrypt_metadata(&key)
.expect("failed to decrypt metadata");
key.set_iv(metadata.iv());
// Parse the metadata response, and decrypt it
(
response.json::<MetadataResponse>()
.expect("failed to parse metadata response")
.decrypt_metadata(&key)
.expect("failed to decrypt metadata"),
nonce,
)
}
/// 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
// TODO: use the metadata nonce here?
// 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");
// Get the download URL, build the download request
// Build and send the download request
// TODO: do not unwrap here, return error
let download_url = self.file.api_download_url();
let mut response = client.get(download_url)
let response = client.get(self.file.api_download_url())
.header(Authorization(
format!("send-v1 {}", sig)
))
@ -164,32 +231,59 @@ impl<'a> Download<'a> {
}
// 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")
.0;
// Open a file to write to, and build an encrypted writer
// TODO: this should become a temporary file first
let out = File::create("downloaded.zip")
.expect("failed to open file");
let writer = EncryptedFileWriter::new(
out,
response_len as usize,
KeySet::cipher(),
key.file_key().unwrap(),
key.iv(),
(response, len)
}
/// Create a file writer.
///
/// This writer will will decrypt the input on the fly, and writes the
/// 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(),
key.file_key().unwrap(),
key.iv(),
).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
}
/// 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
reporter.lock()
.expect("unable to start progress, failed to get lock")
.start(response_len);
.start(len);
// Write to the output file
io::copy(&mut response, &mut writer)
io::copy(&mut reader, &mut writer)
.expect("failed to download and decrypt file");
// Finish
@ -200,158 +294,7 @@ impl<'a> Download<'a> {
// Verify the writer
// TODO: delete the file if verification failed, show a proper error
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.