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::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,
|
||||
(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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue