mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-06 02:29:57 +02:00
Extract API logic to separate project
This commit is contained in:
parent
fdb5a5a8ac
commit
2df0f8d077
14 changed files with 111 additions and 92 deletions
19
api/src/b64.rs
Normal file
19
api/src/b64.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
//! A simple module for encoding or decoding a base64 string from or to a
|
||||
//! byte array.
|
||||
//!
|
||||
//! This module uses an URL-safe scheme, and doesn't add additional padding
|
||||
//! to the encoded strings.
|
||||
|
||||
extern crate base64;
|
||||
|
||||
use self::base64::DecodeError;
|
||||
|
||||
/// Encode the given byte slice using base64, in an URL-safe manner.
|
||||
pub fn encode(input: &[u8]) -> String {
|
||||
base64::encode_config(input, base64::URL_SAFE_NO_PAD)
|
||||
}
|
||||
|
||||
/// Decode the given string as base64, in an URL-safe manner.
|
||||
pub fn decode(input: &str) -> Result<Vec<u8>, DecodeError> {
|
||||
base64::decode_config(input, base64::URL_SAFE_NO_PAD)
|
||||
}
|
56
api/src/crypto.rs
Normal file
56
api/src/crypto.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
extern crate hkdf;
|
||||
extern crate sha2;
|
||||
|
||||
use self::hkdf::Hkdf;
|
||||
use self::sha2::Sha256;
|
||||
|
||||
// Reexport the cryptographically secure random bytes generator
|
||||
pub use super::openssl::rand::rand_bytes;
|
||||
|
||||
/// Derive a HKDF key.
|
||||
///
|
||||
/// No _salt_ bytes are used in this function.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * length - Length of the derived key value that is returned.
|
||||
/// * ikm - The input keying material.
|
||||
/// * info - Optional context and application specific information to use.
|
||||
///
|
||||
/// # Returns
|
||||
/// The output keying material, with the length as as specified in the `length`
|
||||
/// argument.
|
||||
fn hkdf<'a>(
|
||||
length: usize,
|
||||
ikm: &[u8],
|
||||
info: Option<&[u8]>,
|
||||
) -> Vec<u8> {
|
||||
// Unwrap info or use empty info
|
||||
let info = info.unwrap_or(&[]);
|
||||
|
||||
// Derive a HKDF key with the given length
|
||||
Hkdf::<Sha256>::new(&ikm, &[])
|
||||
.derive(&info, length)
|
||||
}
|
||||
|
||||
/// Derive a key to use for file data encryption, based on the given `secret`.
|
||||
pub fn derive_file_key(secret: &[u8]) -> Vec<u8> {
|
||||
hkdf(16, secret, Some(b"encryption"))
|
||||
}
|
||||
|
||||
/// Derive a key to use for metadata encryption, based on the given `secret`.
|
||||
pub fn derive_meta_key(secret: &[u8]) -> Vec<u8> {
|
||||
hkdf(16, secret, Some(b"metadata"))
|
||||
}
|
||||
|
||||
/// Derive a key used for authentication, based on the given `secret`.
|
||||
///
|
||||
/// A `password` and `url` may be given for special key deriving.
|
||||
/// At this time this is not implemented however.
|
||||
pub fn derive_auth_key(secret: &[u8], password: Option<String>, _url: Option<String>) -> Vec<u8> {
|
||||
if password.is_none() {
|
||||
hkdf(64, secret, Some(b"authentication"))
|
||||
} else {
|
||||
// TODO: implement this
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
108
api/src/key_set.rs
Normal file
108
api/src/key_set.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
use openssl::symm::Cipher;
|
||||
|
||||
use b64;
|
||||
use crypto::{derive_auth_key, derive_file_key, derive_meta_key, rand_bytes};
|
||||
|
||||
pub struct KeySet {
|
||||
/// A secret.
|
||||
secret: [u8; 16],
|
||||
|
||||
/// Input vector.
|
||||
iv: [u8; 12],
|
||||
|
||||
/// A derived file encryption key.
|
||||
file_key: Option<Vec<u8>>,
|
||||
|
||||
/// A derived authentication key.
|
||||
auth_key: Option<Vec<u8>>,
|
||||
|
||||
/// A derived metadata key.
|
||||
meta_key: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl KeySet {
|
||||
/// Construct a new key, with the given `secret` and `iv`.
|
||||
pub fn new(secret: [u8; 16], iv: [u8; 12]) -> Self {
|
||||
Self {
|
||||
secret,
|
||||
iv,
|
||||
file_key: None,
|
||||
auth_key: None,
|
||||
meta_key: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a secure new key.
|
||||
///
|
||||
/// If `derive` is `true`, file, authentication and metadata keys will be
|
||||
/// derived from the generated secret.
|
||||
pub fn generate(derive: bool) -> Self {
|
||||
// Allocate two keys
|
||||
let mut secret = [0u8; 16];
|
||||
let mut iv = [0u8; 12];
|
||||
|
||||
// Generate the secrets
|
||||
rand_bytes(&mut secret)
|
||||
.expect("failed to generate crypto secure random secret");
|
||||
rand_bytes(&mut iv)
|
||||
.expect("failed to generate crypto secure random input vector");
|
||||
|
||||
// Create the key
|
||||
let mut key = Self::new(secret, iv);
|
||||
|
||||
// Derive
|
||||
if derive {
|
||||
key.derive();
|
||||
}
|
||||
|
||||
key
|
||||
}
|
||||
|
||||
/// Derive a file, authentication and metadata key.
|
||||
pub fn derive(&mut self) {
|
||||
self.file_key = Some(derive_file_key(&self.secret));
|
||||
self.auth_key = Some(derive_auth_key(&self.secret, None, None));
|
||||
self.meta_key = Some(derive_meta_key(&self.secret));
|
||||
}
|
||||
|
||||
/// Get the secret key.
|
||||
pub fn secret(&self) -> &[u8] {
|
||||
&self.secret
|
||||
}
|
||||
|
||||
/// Get the secret key as URL-safe base64 encoded string.
|
||||
pub fn secret_encoded(&self) -> String {
|
||||
b64::encode(self.secret())
|
||||
}
|
||||
|
||||
/// Get the input vector.
|
||||
pub fn iv(&self) -> &[u8] {
|
||||
&self.iv
|
||||
}
|
||||
|
||||
/// Get the file encryption key, if derived.
|
||||
pub fn file_key(&self) -> Option<&Vec<u8>> {
|
||||
self.file_key.as_ref()
|
||||
}
|
||||
|
||||
/// Get the authentication encryption key, if derived.
|
||||
pub fn auth_key(&self) -> Option<&Vec<u8>> {
|
||||
self.auth_key.as_ref()
|
||||
}
|
||||
|
||||
/// Get the authentication encryption key, if derived,
|
||||
/// as URL-safe base64 encoded string.
|
||||
pub fn auth_key_encoded(&self) -> Option<String> {
|
||||
self.auth_key().map(|key| b64::encode(key))
|
||||
}
|
||||
|
||||
/// Get the metadata encryption key, if derived.
|
||||
pub fn meta_key(&self) -> Option<&Vec<u8>> {
|
||||
self.meta_key.as_ref()
|
||||
}
|
||||
|
||||
/// Get the cipher type to use in combination with these keys.
|
||||
pub fn cipher() -> Cipher {
|
||||
Cipher::aes_128_gcm()
|
||||
}
|
||||
}
|
14
api/src/lib.rs
Normal file
14
api/src/lib.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
extern crate mime_guess;
|
||||
extern crate openssl;
|
||||
extern crate reqwest;
|
||||
pub extern crate url;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod b64;
|
||||
pub mod crypto;
|
||||
pub mod key_set;
|
||||
pub mod metadata;
|
||||
pub mod reader;
|
||||
pub mod send_file;
|
||||
pub mod upload;
|
86
api/src/metadata.rs
Normal file
86
api/src/metadata.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
extern crate hyper;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use mime_guess::Mime;
|
||||
use reqwest::header::{
|
||||
Formatter as HeaderFormatter,
|
||||
Header,
|
||||
Raw,
|
||||
};
|
||||
use self::hyper::error::Error as HyperError;
|
||||
|
||||
use b64;
|
||||
|
||||
/// File metadata, which is send to the server.
|
||||
#[derive(Serialize)]
|
||||
pub struct Metadata {
|
||||
/// The input vector.
|
||||
iv: String,
|
||||
|
||||
/// The file name.
|
||||
name: String,
|
||||
|
||||
/// The file mimetype.
|
||||
#[serde(rename="type")]
|
||||
mime: String,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// Construct metadata from the given properties.
|
||||
///
|
||||
/// Parameters:
|
||||
/// * iv: initialisation vector
|
||||
/// * name: file name
|
||||
/// * mime: file mimetype
|
||||
pub fn from(iv: &[u8], name: String, mime: Mime) -> Self {
|
||||
Metadata {
|
||||
iv: b64::encode(iv),
|
||||
name,
|
||||
mime: mime.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this structure to a JSON string.
|
||||
pub fn to_json(&self) -> String {
|
||||
serde_json::to_string(&self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// A X-File-Metadata header for reqwest, that is used to pass encrypted
|
||||
/// metadata to the server.
|
||||
///
|
||||
/// The encrypted metadata (bytes) is base64 encoded when constructing this
|
||||
/// header using `from`.
|
||||
#[derive(Clone)]
|
||||
pub struct XFileMetadata {
|
||||
/// The metadata, as a base64 encoded string.
|
||||
metadata: String,
|
||||
}
|
||||
|
||||
impl XFileMetadata {
|
||||
/// Construct the header from the given encrypted metadata.
|
||||
pub fn from(bytes: &[u8]) -> Self {
|
||||
XFileMetadata {
|
||||
metadata: b64::encode(bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make this struct usable as reqwest header.
|
||||
impl Header for XFileMetadata {
|
||||
fn header_name() -> &'static str {
|
||||
"X-File-Metadata"
|
||||
}
|
||||
|
||||
fn parse_header(_raw: &Raw) -> Result<Self, HyperError> {
|
||||
// TODO: implement this some time
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn fmt_header(&self, f: &mut HeaderFormatter) -> fmt::Result {
|
||||
// TODO: is this encoding base64 for us?
|
||||
f.fmt_line(&self.metadata)
|
||||
}
|
||||
}
|
220
api/src/reader.rs
Normal file
220
api/src/reader.rs
Normal file
|
@ -0,0 +1,220 @@
|
|||
use std::cmp::min;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Cursor, Read};
|
||||
|
||||
use openssl::symm::{
|
||||
Cipher,
|
||||
Crypter,
|
||||
Mode as CrypterMode,
|
||||
};
|
||||
|
||||
/// The length in bytes of crytographic tags that are used.
|
||||
const TAG_LEN: usize = 16;
|
||||
|
||||
/// A lazy file reader, that encrypts the file with the given `cipher`
|
||||
/// and appends the cryptographic tag to the end of it.
|
||||
///
|
||||
/// This reader is lazy because the file data loaded from the system
|
||||
/// and encrypted when it is read from the reader.
|
||||
/// This greatly reduces memory usage for large files.
|
||||
///
|
||||
/// This reader encrypts the file data with an appended cryptographic tag.
|
||||
///
|
||||
/// 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.
|
||||
/// The excess data is then returned on the next read.
|
||||
pub struct EncryptedFileReaderTagged {
|
||||
/// The raw file that is read from.
|
||||
file: File,
|
||||
|
||||
/// The cipher type used for encrypting.
|
||||
cipher: Cipher,
|
||||
|
||||
/// The crypter used for encrypting the read file.
|
||||
crypter: Crypter,
|
||||
|
||||
/// A tag cursor that reads the tag to append,
|
||||
/// when the file is fully read and the tag is known.
|
||||
tag: Option<Cursor<Vec<u8>>>,
|
||||
|
||||
/// The internal buffer, containing encrypted data that has yet to be
|
||||
/// outputted to the reader. This data is always outputted before any new
|
||||
/// data is produced.
|
||||
internal_buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl EncryptedFileReaderTagged {
|
||||
/// Construct a new reader for the given `file` with the given `cipher`.
|
||||
///
|
||||
/// This method consumes twice the size of the file in memory while
|
||||
/// constructing, and constructs a reader that has a size similar to the
|
||||
/// file.
|
||||
///
|
||||
/// It is recommended to wrap this reader in some sort of buffer, such as:
|
||||
/// `std::io::BufReader`
|
||||
pub fn new(file: File, cipher: Cipher, key: &[u8], iv: &[u8])
|
||||
-> Result<Self, io::Error>
|
||||
{
|
||||
// Build the crypter
|
||||
let crypter = Crypter::new(
|
||||
cipher,
|
||||
CrypterMode::Encrypt,
|
||||
key,
|
||||
Some(iv),
|
||||
)?;
|
||||
|
||||
// Construct the encrypted reader
|
||||
Ok(
|
||||
EncryptedFileReaderTagged {
|
||||
file,
|
||||
cipher,
|
||||
crypter,
|
||||
tag: None,
|
||||
internal_buf: Vec::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculate the total length of the encrypted file with the appended
|
||||
/// tag.
|
||||
/// Useful in combination with some progress monitor, to determine how much
|
||||
/// of the file is read or for example; sent over the network.
|
||||
pub fn len(&self) -> Result<u64, io::Error> {
|
||||
Ok(self.file.metadata()?.len() + TAG_LEN as u64)
|
||||
}
|
||||
|
||||
/// Read data from the internal buffer if there is any data in it, into
|
||||
/// the given `buf`.
|
||||
///
|
||||
/// The number of bytes that were read into `buf` is returned.
|
||||
///
|
||||
/// If there is no data to be read, or `buf` has a zero size, `0` is always
|
||||
/// returned.
|
||||
fn read_internal(&mut self, buf: &mut [u8]) -> usize {
|
||||
// Return if there is no data to read
|
||||
if self.internal_buf.is_empty() || buf.len() == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Determine how much data will be read
|
||||
let len = min(buf.len(), self.internal_buf.len());
|
||||
|
||||
// Slice the section we will read from, copy to the reader
|
||||
{
|
||||
let (out, _) = self.internal_buf.split_at(len);
|
||||
let (buf, _) = buf.split_at_mut(len);
|
||||
buf.copy_from_slice(out);
|
||||
}
|
||||
|
||||
// Drain the read data from the internal buffer
|
||||
self.internal_buf.drain(..len);
|
||||
|
||||
len
|
||||
}
|
||||
|
||||
/// Read data directly from the file, and encrypt it.
|
||||
///
|
||||
/// Because data may be encrypted in blocks, it is possible more data
|
||||
/// is produced than fits in the given `buf`. In that case the excess data
|
||||
/// is stored in an internal buffer, and is ouputted the next time being
|
||||
/// read from the reader.
|
||||
///
|
||||
/// The number of bytes that is read into `buf` is returned.
|
||||
fn read_file_encrypted(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
|
||||
// Get the block size, determine the buffer size, create a data buffer
|
||||
let block_size = self.cipher.block_size();
|
||||
let mut data = vec![0u8; buf.len()];
|
||||
|
||||
// Read the file, return if nothing was read
|
||||
let len = self.file.read(&mut data)?;
|
||||
if len == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Create an encrypted buffer, truncate the data buffer
|
||||
let mut encrypted = vec![0u8; len + block_size];
|
||||
data.truncate(len);
|
||||
|
||||
// Encrypt the data that was read
|
||||
let len = self.crypter.update(&data, &mut encrypted)?;
|
||||
|
||||
// Calculate how many bytes will be copied to the reader
|
||||
let out_len = min(buf.len(), len);
|
||||
|
||||
// Fill the reader buffer
|
||||
let (out, remaining) = encrypted.split_at(out_len);
|
||||
let (buf, _) = buf.split_at_mut(out_len);
|
||||
buf.copy_from_slice(out);
|
||||
|
||||
// Splice to the actual remaining bytes, store it for later
|
||||
let (store, _) = remaining.split_at(len - out_len);
|
||||
self.internal_buf.extend(store.iter());
|
||||
|
||||
// Return the number of bytes read to the reader
|
||||
Ok(out_len)
|
||||
}
|
||||
|
||||
/// Finalize the crypter once it is done encrypthing the whole file.
|
||||
/// This finalization step produces a tag that is placed after the
|
||||
/// encrypted file data.
|
||||
///
|
||||
/// This step must be invoked to start reading the tag,
|
||||
/// and after it has been invoked no data must be encrypted anymore.
|
||||
///
|
||||
/// This method must only be invoked once.
|
||||
fn finalize_file(&mut self) -> Result<(), io::Error> {
|
||||
// Finalize the crypter, catch any remaining output
|
||||
let mut output = vec![0u8; self.cipher.block_size()];
|
||||
let len = self.crypter.finalize(&mut output)?;
|
||||
|
||||
// Move additional output in the internal buffer
|
||||
if len > 0 {
|
||||
self.internal_buf.extend(output.iter().take(len));
|
||||
}
|
||||
|
||||
// Fetch the encryption tag, and create an internal reader for it
|
||||
let mut tag = vec![0u8; TAG_LEN];
|
||||
self.crypter.get_tag(&mut tag)?;
|
||||
self.tag = Some(Cursor::new(tag));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The reader trait implementation.
|
||||
impl Read for EncryptedFileReaderTagged {
|
||||
/// Read from the encrypted file, and then the encryption tag.
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
|
||||
// Read from the internal buffer, return full or splice to empty
|
||||
let len = self.read_internal(buf);
|
||||
if len >= buf.len() {
|
||||
return Ok(len);
|
||||
}
|
||||
let (_, buf) = buf.split_at_mut(len);
|
||||
|
||||
// Keep track of the total number of read bytes, to return
|
||||
let mut total = len;
|
||||
|
||||
// If the tag reader has been created, only read from that one
|
||||
if let Some(ref mut tag) = self.tag {
|
||||
return Ok(tag.read(buf)? + total);
|
||||
}
|
||||
|
||||
// Read the encrypted file, return full or splice to empty
|
||||
let len = self.read_file_encrypted(buf)?;
|
||||
total += len;
|
||||
if len >= buf.len() {
|
||||
return Ok(total);
|
||||
}
|
||||
let (_, buf) = buf.split_at_mut(len);
|
||||
|
||||
// Finalize the file crypter, and build the tag
|
||||
self.finalize_file()?;
|
||||
|
||||
// Try to fill the remaining part of the buffer
|
||||
Ok(self.read(buf)? + total)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement this some other way
|
||||
unsafe impl Send for EncryptedFileReaderTagged {}
|
85
api/src/send_file.rs
Normal file
85
api/src/send_file.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
extern crate chrono;
|
||||
|
||||
use url::Url;
|
||||
use self::chrono::{DateTime, Utc};
|
||||
|
||||
use b64;
|
||||
|
||||
/// A struct representing an uploaded file on a Send host.
|
||||
///
|
||||
/// The struct contains the file ID, the file URL, the key that is required
|
||||
/// in combination with the file, and the owner key.
|
||||
#[derive(Debug)]
|
||||
pub struct SendFile {
|
||||
/// The ID of the file on that server.
|
||||
id: String,
|
||||
|
||||
/// The time the file was uploaded at.
|
||||
time: DateTime<Utc>,
|
||||
|
||||
/// The host the file was uploaded to.
|
||||
host: Url,
|
||||
|
||||
/// The file URL that was provided by the server.
|
||||
url: String,
|
||||
|
||||
/// The secret key that is required to download the file.
|
||||
secret: Vec<u8>,
|
||||
|
||||
/// The owner key, that can be used to manage the file on the server.
|
||||
owner_key: String,
|
||||
}
|
||||
|
||||
impl SendFile {
|
||||
/// Construct a new file.
|
||||
pub fn new(
|
||||
id: String,
|
||||
time: DateTime<Utc>,
|
||||
host: Url,
|
||||
url: String,
|
||||
secret: Vec<u8>,
|
||||
owner_key: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
time,
|
||||
host,
|
||||
url,
|
||||
secret,
|
||||
owner_key,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new file, that was created at this exact time.
|
||||
pub fn new_now(
|
||||
id: String,
|
||||
host: Url,
|
||||
url: String,
|
||||
secret: Vec<u8>,
|
||||
owner_key: String,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
id,
|
||||
Utc::now(),
|
||||
host,
|
||||
url,
|
||||
secret,
|
||||
owner_key,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the raw secret.
|
||||
pub fn secret_raw(&self) -> &Vec<u8> {
|
||||
&self.secret
|
||||
}
|
||||
|
||||
/// Get the secret as base64 encoded string.
|
||||
pub fn secret(&self) -> String {
|
||||
b64::encode(self.secret_raw())
|
||||
}
|
||||
|
||||
/// Get the download URL of the file, with the secret key included.
|
||||
pub fn download_url(&self) -> String {
|
||||
format!("{}#{}", self.url, self.secret())
|
||||
}
|
||||
}
|
153
api/src/upload.rs
Normal file
153
api/src/upload.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
|
||||
use mime_guess::get_mime_type;
|
||||
use openssl::symm::encrypt_aead;
|
||||
use reqwest;
|
||||
use reqwest::header::Authorization;
|
||||
use reqwest::mime::APPLICATION_OCTET_STREAM;
|
||||
use reqwest::multipart::Part;
|
||||
use url::Url;
|
||||
|
||||
use super::key_set::KeySet;
|
||||
use super::metadata::{Metadata, XFileMetadata};
|
||||
use super::reader::EncryptedFileReaderTagged;
|
||||
use super::send_file::SendFile;
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, UploadError>;
|
||||
|
||||
/// A file upload action to a Send server.
|
||||
pub struct Upload {
|
||||
/// The Send host to upload the file to.
|
||||
host: Url,
|
||||
|
||||
/// The file to upload.
|
||||
path: Box<Path>,
|
||||
}
|
||||
|
||||
impl Upload {
|
||||
/// Construct a new upload action.
|
||||
pub fn new(host: Url, path: Box<Path>) -> Self {
|
||||
Self {
|
||||
host,
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke the upload action.
|
||||
pub fn invoke(self) -> Result<SendFile> {
|
||||
// Make sure the given path is a file
|
||||
if !self.path.is_file() {
|
||||
return Err(UploadError::NotAFile);
|
||||
}
|
||||
|
||||
// Grab some file details
|
||||
let file_ext = self.path.extension().unwrap().to_str().unwrap();
|
||||
let file_name = self.path.file_name().unwrap().to_str().unwrap().to_owned();
|
||||
let file_mime = get_mime_type(file_ext);
|
||||
|
||||
// Generate a key set
|
||||
let key = KeySet::generate(true);
|
||||
|
||||
// Construct the metadata
|
||||
let metadata = Metadata::from(key.iv(), file_name.clone(), file_mime)
|
||||
.to_json()
|
||||
.into_bytes();
|
||||
|
||||
// Encrypt the metadata, and append the tag to it
|
||||
let mut metadata_tag = vec![0u8; 16];
|
||||
let mut metadata = encrypt_aead(
|
||||
KeySet::cipher(),
|
||||
key.meta_key().unwrap(),
|
||||
Some(&[0u8; 12]),
|
||||
&[],
|
||||
&metadata,
|
||||
&mut metadata_tag,
|
||||
).unwrap();
|
||||
metadata.append(&mut metadata_tag);
|
||||
|
||||
// Open the file and create an encrypted file reader
|
||||
let file = File::open(&self.path).unwrap();
|
||||
let reader = EncryptedFileReaderTagged::new(
|
||||
file,
|
||||
KeySet::cipher(),
|
||||
key.file_key().unwrap(),
|
||||
key.iv(),
|
||||
).unwrap();
|
||||
|
||||
// Buffer the encrypted reader, and determine the length
|
||||
let reader_len = reader.len().unwrap();
|
||||
let reader = BufReader::new(reader);
|
||||
|
||||
// Build the file part, configure the form to send
|
||||
let part = Part::reader_with_length(reader, reader_len)
|
||||
.file_name(file_name)
|
||||
.mime(APPLICATION_OCTET_STREAM);
|
||||
let form = reqwest::multipart::Form::new()
|
||||
.part("data", part);
|
||||
|
||||
// Create a new reqwest client
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
// Make the request
|
||||
// TODO: properly format an URL here
|
||||
let url = self.host.join("api/upload").expect("invalid host");
|
||||
let mut res = client.post(url.as_str())
|
||||
.header(Authorization(format!("send-v1 {}", key.auth_key_encoded().unwrap())))
|
||||
.header(XFileMetadata::from(&metadata))
|
||||
.multipart(form)
|
||||
.send()
|
||||
.unwrap();
|
||||
|
||||
// Parse the response
|
||||
let upload_res: UploadResponse = res.json().unwrap();
|
||||
|
||||
// Print the response
|
||||
Ok(
|
||||
upload_res.into_file(self.host, key.secret().to_vec())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum UploadError {
|
||||
/// The given file is not not an existing file.
|
||||
/// Maybe it is a directory, or maybe it doesn't exist.
|
||||
NotAFile,
|
||||
}
|
||||
|
||||
/// The response from the server after a file has been uploaded.
|
||||
/// This response contains the file ID and owner key, to manage the file.
|
||||
///
|
||||
/// It also contains the download URL, although an additional secret is
|
||||
/// required.
|
||||
///
|
||||
/// The download URL can be generated using `download_url()` which will
|
||||
/// include the required secret in the URL.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UploadResponse {
|
||||
/// The file ID.
|
||||
id: String,
|
||||
|
||||
/// The URL the file is reachable at.
|
||||
/// This includes the file ID, but does not include the secret.
|
||||
url: String,
|
||||
|
||||
/// The owner key, used to do further file modifications.
|
||||
owner: String,
|
||||
}
|
||||
|
||||
impl UploadResponse {
|
||||
/// Convert this response into a file object.
|
||||
///
|
||||
/// The `host` and `secret` must be given.
|
||||
pub fn into_file(self, host: Url, secret: Vec<u8>) -> SendFile {
|
||||
SendFile::new_now(
|
||||
self.id,
|
||||
host,
|
||||
self.url,
|
||||
secret,
|
||||
self.owner,
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue