mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-05 10:19:23 +02:00
Implement file download and decrypt logic
This commit is contained in:
parent
7e22c07d72
commit
9eb9462c40
11 changed files with 271 additions and 95 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -242,6 +242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
name = "ffsend-api"
|
name = "ffsend-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hkdf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hkdf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -5,6 +5,7 @@ authors = ["timvisee <timvisee@gmail.com>"]
|
||||||
workspace = ".."
|
workspace = ".."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
arrayref = "0.3"
|
||||||
base64 = "0.9"
|
base64 = "0.9"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
hkdf = "0.3"
|
hkdf = "0.3"
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use std::path::Path;
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
use mime_guess::{get_mime_type, Mime};
|
|
||||||
use openssl::symm::decrypt_aead;
|
use openssl::symm::decrypt_aead;
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
Client,
|
Client,
|
||||||
Error as ReqwestError,
|
Error as ReqwestError,
|
||||||
};
|
};
|
||||||
use reqwest::header::Authorization;
|
use reqwest::header::Authorization;
|
||||||
|
use reqwest::header::ContentLength;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
use crypto::b64;
|
use crypto::b64;
|
||||||
|
@ -14,6 +15,7 @@ use crypto::key_set::KeySet;
|
||||||
use crypto::sign::signature_encoded;
|
use crypto::sign::signature_encoded;
|
||||||
use file::file::DownloadFile;
|
use file::file::DownloadFile;
|
||||||
use file::metadata::Metadata;
|
use file::metadata::Metadata;
|
||||||
|
use reader::EncryptedFileWriter;
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, DownloadError>;
|
pub type Result<T> = ::std::result::Result<T, DownloadError>;
|
||||||
|
|
||||||
|
@ -42,7 +44,7 @@ impl<'a> Download<'a> {
|
||||||
client: &Client,
|
client: &Client,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Create a key set for the file
|
// Create a key set for the file
|
||||||
let key = KeySet::from(self.file);
|
let mut key = KeySet::from(self.file);
|
||||||
|
|
||||||
// Build the meta cipher
|
// Build the meta cipher
|
||||||
// let mut metadata_tag = vec![0u8; 16];
|
// let mut metadata_tag = vec![0u8; 16];
|
||||||
|
@ -92,7 +94,7 @@ impl<'a> Download<'a> {
|
||||||
// Compute the cryptographic signature
|
// Compute the cryptographic signature
|
||||||
// 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(), &nonce)
|
||||||
.expect("failed to compute signature");
|
.expect("failed to compute metadata signature");
|
||||||
|
|
||||||
// Get the meta URL, fetch the metadata
|
// Get the meta URL, fetch the metadata
|
||||||
// TODO: do not unwrap here, return error
|
// TODO: do not unwrap here, return error
|
||||||
|
@ -126,14 +128,63 @@ impl<'a> Download<'a> {
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.next()
|
.next()
|
||||||
.expect("missing metadata nonce")
|
.expect("missing metadata nonce")
|
||||||
);
|
).expect("failed to decode metadata nonce");
|
||||||
|
|
||||||
// Parse the metadata response
|
// Parse the metadata response
|
||||||
let meta_response: MetadataResponse = response.json()
|
let meta_response: MetadataResponse = response.json()
|
||||||
.expect("failed to parse metadata response");
|
.expect("failed to parse metadata response");
|
||||||
|
|
||||||
// Decrypt the metadata
|
// Decrypt the metadata, set the input vector
|
||||||
let metadata = meta_response.decrypt_metadata(&key);
|
let metadata = meta_response.decrypt_metadata(&key)
|
||||||
|
.expect("failed to decrypt metadata");
|
||||||
|
key.set_iv(metadata.iv());
|
||||||
|
|
||||||
|
// Compute the cryptographic signature
|
||||||
|
// TODO: do not unwrap, return an error
|
||||||
|
let sig = signature_encoded(key.auth_key().unwrap(), &nonce)
|
||||||
|
.expect("failed to compute file signature");
|
||||||
|
|
||||||
|
// Get the download URL, build 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)
|
||||||
|
.header(Authorization(
|
||||||
|
format!("send-v1 {}", sig)
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.expect("failed to fetch file, failed to send request");
|
||||||
|
|
||||||
|
// Validate the status code
|
||||||
|
// TODO: allow redirects here?
|
||||||
|
if !response.status().is_success() {
|
||||||
|
// TODO: return error here
|
||||||
|
panic!("failed to fetch file, request status is not successful");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the content length
|
||||||
|
let response_len = response.headers().get::<ContentLength>()
|
||||||
|
.expect("failed to fetch file, missing content length header")
|
||||||
|
.0;
|
||||||
|
|
||||||
|
// Open a file to write to
|
||||||
|
// TODO: this should become a temporary file first
|
||||||
|
let out = File::create("downloaded.toml")
|
||||||
|
.expect("failed to open file");
|
||||||
|
let mut writer = EncryptedFileWriter::new(
|
||||||
|
out,
|
||||||
|
response_len as usize,
|
||||||
|
KeySet::cipher(),
|
||||||
|
key.file_key().unwrap(),
|
||||||
|
key.iv(),
|
||||||
|
).expect("failed to create encrypted writer");
|
||||||
|
|
||||||
|
// Write to the output file
|
||||||
|
io::copy(&mut response, &mut writer)
|
||||||
|
.expect("failed to download and decrypt file");
|
||||||
|
|
||||||
|
// Verify the writer
|
||||||
|
// TODO: delete the file if verification failed, show a proper error
|
||||||
|
assert!(writer.verified(), "downloaded and decrypted file could not be verified");
|
||||||
|
|
||||||
// // Crpate metadata and a file reader
|
// // Crpate metadata and a file reader
|
||||||
// let metadata = self.create_metadata(&key, &file)?;
|
// let metadata = self.create_metadata(&key, &file)?;
|
||||||
|
@ -160,6 +211,9 @@ impl<'a> Download<'a> {
|
||||||
// reporter.lock()
|
// reporter.lock()
|
||||||
// .expect("unable to finish progress, failed to get lock")
|
// .expect("unable to finish progress, failed to get lock")
|
||||||
// .finish();
|
// .finish();
|
||||||
|
|
||||||
|
// TODO: return the file path
|
||||||
|
// TODO: return the new remote state (does it still exist remote)
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -352,53 +406,3 @@ impl MetadataResponse {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// A struct that holds various file properties, such as it's name and it's
|
|
||||||
// /// mime type.
|
|
||||||
// struct FileData<'a> {
|
|
||||||
// /// The file name.
|
|
||||||
// name: &'a str,
|
|
||||||
|
|
||||||
// /// The file mime type.
|
|
||||||
// mime: Mime,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl<'a> FileData<'a> {
|
|
||||||
// /// Create a file data object, from the file at the given path.
|
|
||||||
// pub fn from(path: Box<&'a Path>) -> Result<Self> {
|
|
||||||
// // Make sure the given path is a file
|
|
||||||
// if !path.is_file() {
|
|
||||||
// return Err(DownloadError::NotAFile);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Get the file name
|
|
||||||
// let name = match path.file_name() {
|
|
||||||
// Some(name) => name.to_str().expect("failed to convert string"),
|
|
||||||
// None => return Err(DownloadError::FileError),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Get the file extention
|
|
||||||
// // TODO: handle cases where the file doesn't have an extention
|
|
||||||
// let ext = match path.extension() {
|
|
||||||
// Some(ext) => ext.to_str().expect("failed to convert string"),
|
|
||||||
// None => return Err(DownloadError::FileError),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Ok(
|
|
||||||
// Self {
|
|
||||||
// name,
|
|
||||||
// mime: get_mime_type(ext),
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Get the file name.
|
|
||||||
// pub fn name(&self) -> &str {
|
|
||||||
// self.name
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Get the file mime type.
|
|
||||||
// pub fn mime(&self) -> &Mime {
|
|
||||||
// &self.mime
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ use url::Url;
|
||||||
|
|
||||||
use crypto::key_set::KeySet;
|
use crypto::key_set::KeySet;
|
||||||
use reader::{
|
use reader::{
|
||||||
EncryptedFileReaderTagged,
|
EncryptedFileReader,
|
||||||
ExactLengthReader,
|
ExactLengthReader,
|
||||||
ProgressReader,
|
ProgressReader,
|
||||||
ProgressReporter,
|
ProgressReporter,
|
||||||
|
@ -25,7 +25,7 @@ use reader::{
|
||||||
use file::file::File as SendFile;
|
use file::file::File as SendFile;
|
||||||
use file::metadata::{Metadata, XFileMetadata};
|
use file::metadata::{Metadata, XFileMetadata};
|
||||||
|
|
||||||
type EncryptedReader = ProgressReader<BufReader<EncryptedFileReaderTagged>>;
|
type EncryptedReader = ProgressReader<BufReader<EncryptedFileReader>>;
|
||||||
pub type Result<T> = ::std::result::Result<T, UploadError>;
|
pub type Result<T> = ::std::result::Result<T, UploadError>;
|
||||||
|
|
||||||
/// A file upload action to a Send server.
|
/// A file upload action to a Send server.
|
||||||
|
@ -129,7 +129,7 @@ impl Upload {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create an encrypted reader
|
// Create an encrypted reader
|
||||||
let reader = match EncryptedFileReaderTagged::new(
|
let reader = match EncryptedFileReader::new(
|
||||||
file,
|
file,
|
||||||
KeySet::cipher(),
|
KeySet::cipher(),
|
||||||
key.file_key().unwrap(),
|
key.file_key().unwrap(),
|
||||||
|
|
|
@ -104,6 +104,11 @@ impl KeySet {
|
||||||
&self.iv
|
&self.iv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the input vector.
|
||||||
|
pub fn set_iv(&mut self, iv: [u8; KEY_IV_LEN]) {
|
||||||
|
self.iv = iv;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the file encryption key, if derived.
|
/// Get the file encryption key, if derived.
|
||||||
pub fn file_key(&self) -> Option<&Vec<u8>> {
|
pub fn file_key(&self) -> Option<&Vec<u8>> {
|
||||||
self.file_key.as_ref()
|
self.file_key.as_ref()
|
||||||
|
|
|
@ -246,6 +246,16 @@ impl DownloadFile {
|
||||||
|
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the API download URL of the file.
|
||||||
|
pub fn api_download_url(&self) -> Url {
|
||||||
|
// Get the download URL, and add the secret fragment
|
||||||
|
let mut url = self.url.clone();
|
||||||
|
url.set_path(format!("/api/download/{}", self.id).as_str());
|
||||||
|
url.set_fragment(None);
|
||||||
|
|
||||||
|
url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -46,6 +46,16 @@ impl Metadata {
|
||||||
pub fn to_json(&self) -> String {
|
pub fn to_json(&self) -> String {
|
||||||
serde_json::to_string(&self).unwrap()
|
serde_json::to_string(&self).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the input vector
|
||||||
|
// TODO: use an input vector length from a constant
|
||||||
|
pub fn iv(&self) -> [u8; 12] {
|
||||||
|
// Decode the input vector
|
||||||
|
let decoded = b64::decode_url(&self.iv).unwrap();
|
||||||
|
|
||||||
|
// Create a sized array
|
||||||
|
*array_ref!(decoded, 0, 12)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A X-File-Metadata header for reqwest, that is used to pass encrypted
|
/// A X-File-Metadata header for reqwest, that is used to pass encrypted
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate arrayref;
|
||||||
extern crate mime_guess;
|
extern crate mime_guess;
|
||||||
extern crate openssl;
|
extern crate openssl;
|
||||||
pub extern crate reqwest;
|
pub extern crate reqwest;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::cmp::min;
|
use std::cmp::{max, min};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{
|
use std::io::{
|
||||||
self,
|
self,
|
||||||
|
@ -6,6 +6,7 @@ use std::io::{
|
||||||
Cursor,
|
Cursor,
|
||||||
Error as IoError,
|
Error as IoError,
|
||||||
Read,
|
Read,
|
||||||
|
Write,
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
@ -18,6 +19,8 @@ use openssl::symm::{
|
||||||
/// The length in bytes of crytographic tags that are used.
|
/// The length in bytes of crytographic tags that are used.
|
||||||
const TAG_LEN: usize = 16;
|
const TAG_LEN: usize = 16;
|
||||||
|
|
||||||
|
// TODO: create a generic reader/writer wrapper for the the encryptor/decryptor.
|
||||||
|
|
||||||
/// A lazy file reader, that encrypts the file with the given `cipher`
|
/// A lazy file reader, that encrypts the file with the given `cipher`
|
||||||
/// and appends the cryptographic tag to the end of it.
|
/// and appends the cryptographic tag to the end of it.
|
||||||
///
|
///
|
||||||
|
@ -30,7 +33,7 @@ const TAG_LEN: usize = 16;
|
||||||
/// The reader uses a small internal buffer as data is encrypted in blocks,
|
/// The reader uses a small internal buffer as data is encrypted in blocks,
|
||||||
/// which may output more data than fits in the given buffer while reading.
|
/// which may output more data than fits in the given buffer while reading.
|
||||||
/// The excess data is then returned on the next read.
|
/// The excess data is then returned on the next read.
|
||||||
pub struct EncryptedFileReaderTagged {
|
pub struct EncryptedFileReader {
|
||||||
/// The raw file that is read from.
|
/// The raw file that is read from.
|
||||||
file: File,
|
file: File,
|
||||||
|
|
||||||
|
@ -50,7 +53,7 @@ pub struct EncryptedFileReaderTagged {
|
||||||
internal_buf: Vec<u8>,
|
internal_buf: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EncryptedFileReaderTagged {
|
impl EncryptedFileReader {
|
||||||
/// Construct a new reader for the given `file` with the given `cipher`.
|
/// Construct a new reader for the given `file` with the given `cipher`.
|
||||||
///
|
///
|
||||||
/// This method consumes twice the size of the file in memory while
|
/// This method consumes twice the size of the file in memory while
|
||||||
|
@ -72,7 +75,7 @@ impl EncryptedFileReaderTagged {
|
||||||
|
|
||||||
// Construct the encrypted reader
|
// Construct the encrypted reader
|
||||||
Ok(
|
Ok(
|
||||||
EncryptedFileReaderTagged {
|
EncryptedFileReader {
|
||||||
file,
|
file,
|
||||||
cipher,
|
cipher,
|
||||||
crypter,
|
crypter,
|
||||||
|
@ -180,7 +183,7 @@ impl EncryptedFileReaderTagged {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExactLengthReader for EncryptedFileReaderTagged {
|
impl ExactLengthReader for EncryptedFileReader {
|
||||||
/// Calculate the total length of the encrypted file with the appended
|
/// Calculate the total length of the encrypted file with the appended
|
||||||
/// tag.
|
/// tag.
|
||||||
/// Useful in combination with some progress monitor, to determine how much
|
/// Useful in combination with some progress monitor, to determine how much
|
||||||
|
@ -191,7 +194,7 @@ impl ExactLengthReader for EncryptedFileReaderTagged {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The reader trait implementation.
|
/// The reader trait implementation.
|
||||||
impl Read for EncryptedFileReaderTagged {
|
impl Read for EncryptedFileReader {
|
||||||
/// Read from the encrypted file, and then the encryption tag.
|
/// Read from the encrypted file, and then the encryption tag.
|
||||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
|
||||||
// Read from the internal buffer, return full or splice to empty
|
// Read from the internal buffer, return full or splice to empty
|
||||||
|
@ -226,7 +229,7 @@ impl Read for EncryptedFileReaderTagged {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement this some other way
|
// TODO: implement this some other way
|
||||||
unsafe impl Send for EncryptedFileReaderTagged {}
|
unsafe impl Send for EncryptedFileReader {}
|
||||||
|
|
||||||
/// A reader wrapper, that measures the reading process for a reader with a
|
/// A reader wrapper, that measures the reading process for a reader with a
|
||||||
/// known length.
|
/// known length.
|
||||||
|
@ -341,3 +344,168 @@ impl<R: ExactLengthReader> ExactLengthReader for BufReader<R> {
|
||||||
self.get_ref().len()
|
self.get_ref().len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A lazy file writer, that decrypt the file with the given `cipher`
|
||||||
|
/// and verifies it with the tag appended to the end of the input data.
|
||||||
|
///
|
||||||
|
/// This writer is lazy because the input data is decrypted and written to the
|
||||||
|
/// specified file on the fly, instead of buffering all the data first.
|
||||||
|
/// This greatly reduces memory usage for large files.
|
||||||
|
///
|
||||||
|
/// The length of the input data (including the appended tag) must be given
|
||||||
|
/// when this reader is initialized. When all data including the tag is read,
|
||||||
|
/// the decrypted data is verified with the tag. If the tag doesn't match the
|
||||||
|
/// decrypted data, a write error is returned on the last write.
|
||||||
|
/// This writer will never write more bytes than the length initially
|
||||||
|
/// specified.
|
||||||
|
///
|
||||||
|
/// This reader encrypts the input data with the given key and input vector.
|
||||||
|
///
|
||||||
|
/// A failed writing implies that no data could be written, or that the data
|
||||||
|
/// wasn't successfully decrypted because of an decryption or tag matching
|
||||||
|
/// error. Such a fail means that the file will be incomplete or corrupted,
|
||||||
|
/// and should therefore be removed from the disk.
|
||||||
|
///
|
||||||
|
/// It is highly recommended to invoke the `verified()` method after writing
|
||||||
|
/// the file, to ensure the written file is indeed complete and fully verified.
|
||||||
|
pub struct EncryptedFileWriter {
|
||||||
|
/// The file to write the decrypted data to.
|
||||||
|
file: File,
|
||||||
|
|
||||||
|
/// The number of bytes that have currently been written to this writer.
|
||||||
|
cur: usize,
|
||||||
|
|
||||||
|
/// The length of all the data, which includes the file data and the
|
||||||
|
/// appended tag.
|
||||||
|
len: usize,
|
||||||
|
|
||||||
|
/// The cipher type used for decrypting.
|
||||||
|
cipher: Cipher,
|
||||||
|
|
||||||
|
/// The crypter used for decrypting the data.
|
||||||
|
crypter: Crypter,
|
||||||
|
|
||||||
|
/// A buffer for the tag.
|
||||||
|
tag_buf: Vec<u8>,
|
||||||
|
|
||||||
|
/// A boolean that defines whether the decrypted data has successfully
|
||||||
|
/// been verified.
|
||||||
|
verified: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncryptedFileWriter {
|
||||||
|
/// Construct a new encrypted file writer.
|
||||||
|
///
|
||||||
|
/// The file to write to must be given to `file`, which must be open for
|
||||||
|
/// writing. The total length of the input data in bytes must be given to
|
||||||
|
/// `len`, which includes both the file bytes and the appended tag.
|
||||||
|
///
|
||||||
|
/// For decryption, a `cipher`, `key` and `iv` must also be given.
|
||||||
|
pub fn new(file: File, len: usize, cipher: Cipher, key: &[u8], iv: &[u8])
|
||||||
|
-> Result<Self, io::Error>
|
||||||
|
{
|
||||||
|
// Build the crypter
|
||||||
|
let crypter = Crypter::new(
|
||||||
|
cipher,
|
||||||
|
CrypterMode::Decrypt,
|
||||||
|
key,
|
||||||
|
Some(iv),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Construct the encrypted reader
|
||||||
|
Ok(
|
||||||
|
EncryptedFileWriter {
|
||||||
|
file,
|
||||||
|
cur: 0,
|
||||||
|
len,
|
||||||
|
cipher,
|
||||||
|
crypter,
|
||||||
|
tag_buf: Vec::with_capacity(TAG_LEN),
|
||||||
|
verified: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check wheher the complete tag is buffered.
|
||||||
|
pub fn has_tag(&self) -> bool {
|
||||||
|
self.tag_buf.len() >= TAG_LEN
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the decrypted data is succesfsully verified.
|
||||||
|
///
|
||||||
|
/// If this method returns true the following is implied:
|
||||||
|
/// - The complete file has been written.
|
||||||
|
/// - The complete file was successfully decrypted.
|
||||||
|
/// - The included tag matches the decrypted file.
|
||||||
|
///
|
||||||
|
/// It is highly recommended to invoke this method and check the
|
||||||
|
/// verification after writing the file using this writer.
|
||||||
|
pub fn verified(&self) -> bool {
|
||||||
|
self.verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The writer trait implementation.
|
||||||
|
impl Write for EncryptedFileWriter {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
|
||||||
|
// Do not write anything if the tag was already written
|
||||||
|
if self.verified() || self.has_tag() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine how many file and tag bytes we still need to process
|
||||||
|
let file_bytes = max(self.len - TAG_LEN - self.cur, 0);
|
||||||
|
let tag_bytes = TAG_LEN - self.tag_buf.len();
|
||||||
|
|
||||||
|
// Split the input buffer
|
||||||
|
let (file_buf, tag_buf) = buf.split_at(min(file_bytes, buf.len()));
|
||||||
|
|
||||||
|
// Read from the file buf
|
||||||
|
if !file_buf.is_empty() {
|
||||||
|
// Create a decrypted buffer, with the proper size
|
||||||
|
let block_size = self.cipher.block_size();
|
||||||
|
let mut decrypted = vec![0u8; file_bytes + block_size];
|
||||||
|
|
||||||
|
// Decrypt bytes
|
||||||
|
// TODO: catch error in below statement
|
||||||
|
let len = self.crypter.update(
|
||||||
|
file_buf,
|
||||||
|
&mut decrypted,
|
||||||
|
)?;
|
||||||
|
decrypted.truncate(len);
|
||||||
|
|
||||||
|
// Write to the file
|
||||||
|
self.file.write_all(&decrypted)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read from the tag part to fill the tag buffer
|
||||||
|
if !tag_buf.is_empty() {
|
||||||
|
self.tag_buf.extend(tag_buf.iter().take(tag_bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the tag once it has been buffered completely
|
||||||
|
if self.has_tag() {
|
||||||
|
// Set the tag
|
||||||
|
self.crypter.set_tag(&self.tag_buf)?;
|
||||||
|
|
||||||
|
// Create a buffer for any remaining data
|
||||||
|
let block_size = self.cipher.block_size();
|
||||||
|
let mut extra = vec![0u8; block_size];
|
||||||
|
|
||||||
|
// Finalize, write all remaining data
|
||||||
|
let len = self.crypter.finalize(&mut extra)?;
|
||||||
|
extra.truncate(len);
|
||||||
|
self.file.write_all(&extra)?;
|
||||||
|
|
||||||
|
// Set the verified flag
|
||||||
|
self.verified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute how many bytes were written
|
||||||
|
Ok(file_bytes - file_buf.len() + min(tag_buf.len(), tag_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), io::Error> {
|
||||||
|
self.file.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use ffsend_api::action::download::Download as ApiDownload;
|
use ffsend_api::action::download::Download as ApiDownload;
|
||||||
use ffsend_api::file::file::DownloadFile;
|
use ffsend_api::file::file::DownloadFile;
|
||||||
use ffsend_api::reqwest::Client;
|
use ffsend_api::reqwest::Client;
|
||||||
|
|
||||||
use cmd::cmd_download::CmdDownload;
|
use cmd::cmd_download::CmdDownload;
|
||||||
use progress::ProgressBar;
|
|
||||||
use util::open_url;
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
use util::set_clipboard;
|
|
||||||
|
|
||||||
/// A file download action.
|
/// A file download action.
|
||||||
pub struct Download<'a> {
|
pub struct Download<'a> {
|
||||||
|
@ -41,26 +34,9 @@ impl<'a> Download<'a> {
|
||||||
// TODO: do not unwrap, but return an error
|
// TODO: do not unwrap, but return an error
|
||||||
ApiDownload::new(&file).invoke(&client).unwrap();
|
ApiDownload::new(&file).invoke(&client).unwrap();
|
||||||
|
|
||||||
// // Get the download URL, and report it in the console
|
// TODO: open the file, or it's location
|
||||||
// let url = file.download_url(true);
|
// TODO: copy the file location
|
||||||
// println!("Download URL: {}", url);
|
|
||||||
|
|
||||||
// // Open the URL in the browser
|
println!("Download complete");
|
||||||
// if self.cmd.open() {
|
|
||||||
// // TODO: do not expect, but return an error
|
|
||||||
// open_url(url.clone()).expect("failed to open URL");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Copy the URL in the user's clipboard
|
|
||||||
// #[cfg(feature = "clipboard")]
|
|
||||||
// {
|
|
||||||
// if self.cmd.copy() {
|
|
||||||
// // TODO: do not expect, but return an error
|
|
||||||
// set_clipboard(url.as_str().to_owned())
|
|
||||||
// .expect("failed to put download URL in user clipboard");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
panic!("DONE");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ use ffsend_api::url::{ParseError, Url};
|
||||||
|
|
||||||
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
|
||||||
use app::SEND_DEF_HOST;
|
|
||||||
use util::quit_error;
|
use util::quit_error;
|
||||||
|
|
||||||
/// The download command.
|
/// The download command.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue