extern crate mime;
use std::fs::File;
use std::io::{BufReader, Error as IoError};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use self::mime::APPLICATION_OCTET_STREAM;
use crypto::b64;
use mime_guess::{guess_mime_type, Mime};
use openssl::symm::encrypt_aead;
use reqwest::header::AUTHORIZATION;
use reqwest::multipart::{Form, Part};
use reqwest::{Client, Error as ReqwestError, Request};
use url::{ParseError as UrlParseError, Url};
use super::params::{Error as ParamsError, Params, ParamsData};
use super::password::{Error as PasswordError, Password};
use api::nonce::header_nonce;
use api::request::{ensure_success, ResponseError};
use crypto::key_set::KeySet;
use file::metadata::Metadata;
use file::remote_file::RemoteFile;
use reader::{EncryptedFileReader, ExactLengthReader, ProgressReader, ProgressReporter};
type EncryptedReader = ProgressReader<BufReader<EncryptedFileReader>>;
pub struct Upload {
host: Url,
path: PathBuf,
name: Option<String>,
password: Option<String>,
params: Option<ParamsData>,
}
impl Upload {
pub fn new(
host: Url,
path: PathBuf,
name: Option<String>,
password: Option<String>,
params: Option<ParamsData>,
) -> Self {
Self {
host,
path,
name,
password,
params,
}
}
pub fn invoke(
self,
client: &Client,
reporter: &Arc<Mutex<ProgressReporter>>,
) -> Result<RemoteFile, Error> {
let file = FileData::from(&self.path)?;
let key = KeySet::generate(true);
let metadata = self.create_metadata(&key, &file)?;
let reader = self.create_reader(&key, reporter.clone())?;
let reader_len = reader.len().unwrap();
let req = self.create_request(client, &key, &metadata, reader);
reporter
.lock()
.map_err(|_| UploadError::Progress)?
.start(reader_len);
let (result, nonce) = self.execute_request(req, client, &key)?;
reporter.lock().map_err(|_| UploadError::Progress)?.finish();
if let Some(password) = self.password {
Password::new(&result, &password, nonce.clone()).invoke(client)?;
}
if let Some(params) = self.params {
Params::new(&result, params, nonce.clone()).invoke(client)?;
}
Ok(result)
}
fn create_metadata(&self, key: &KeySet, file: &FileData) -> Result<Vec<u8>, MetaError> {
let name = self.name.clone().unwrap_or_else(|| file.name().to_owned());
let metadata = Metadata::from(key.iv(), name, &file.mime())
.to_json()
.into_bytes();
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(MetaError::Encrypt),
};
metadata.append(&mut metadata_tag);
Ok(metadata)
}
fn create_reader(
&self,
key: &KeySet,
reporter: Arc<Mutex<ProgressReporter>>,
) -> Result<EncryptedReader, Error> {
let file = match File::open(self.path.as_path()) {
Ok(file) => file,
Err(err) => return Err(FileError::Open(err).into()),
};
let reader = match EncryptedFileReader::new(
file,
KeySet::cipher(),
key.file_key().unwrap(),
key.iv(),
) {
Ok(reader) => reader,
Err(_) => return Err(ReaderError::Encrypt.into()),
};
let reader = BufReader::new(reader);
let mut reader = ProgressReader::new(reader).map_err(|_| ReaderError::Progress)?;
reader.set_reporter(reporter);
Ok(reader)
}
fn create_request(
&self,
client: &Client,
key: &KeySet,
metadata: &[u8],
reader: EncryptedReader,
) -> Request {
let len = reader.len().expect("failed to get reader length");
let part = Part::reader_with_length(reader, len)
.mime_str(APPLICATION_OCTET_STREAM.as_ref())
.expect("failed to set request mime");
let form = Form::new().part("data", part);
let url = self.host.join("api/upload").expect("invalid host");
client
.post(url.as_str())
.header(
AUTHORIZATION.as_str(),
format!("send-v1 {}", key.auth_key_encoded().unwrap()),
)
.header("X-File-Metadata", b64::encode(&metadata))
.multipart(form)
.build()
.expect("failed to build an API request")
}
fn execute_request(
&self,
req: Request,
client: &Client,
key: &KeySet,
) -> Result<(RemoteFile, Option<Vec<u8>>), UploadError> {
let mut response = match client.execute(req) {
Ok(response) => response,
Err(_) => return Err(UploadError::Request),
};
ensure_success(&response).map_err(UploadError::Response)?;
let nonce = header_nonce(&response).ok();
let response: UploadResponse = match response.json() {
Ok(response) => response,
Err(err) => return Err(UploadError::Decode(err)),
};
Ok((response.into_file(self.host.clone(), &key)?, nonce))
}
}
#[derive(Debug, Deserialize)]
struct UploadResponse {
id: String,
url: String,
owner: String,
}
impl UploadResponse {
pub fn into_file(self, host: Url, key: &KeySet) -> Result<RemoteFile, UploadError> {
Ok(RemoteFile::new_now(
self.id,
host,
Url::parse(&self.url)?,
key.secret().to_vec(),
Some(self.owner),
))
}
}
struct FileData<'a> {
name: &'a str,
mime: Mime,
}
impl<'a> FileData<'a> {
pub fn from(path: &'a PathBuf) -> Result<Self, FileError> {
if !path.is_file() {
return Err(FileError::NotAFile);
}
let name = match path.file_name() {
Some(name) => name.to_str().unwrap_or("file"),
None => "file",
};
Ok(Self {
name,
mime: guess_mime_type(path),
})
}
pub fn name(&self) -> &str {
self.name
}
pub fn mime(&self) -> &Mime {
&self.mime
}
}
#[derive(Fail, Debug)]
pub enum Error {
#[fail(display = "failed to prepare uploading the file")]
Prepare(#[cause] PrepareError),
#[fail(display = "")]
File(#[cause] FileError),
#[fail(display = "failed to upload the file")]
Upload(#[cause] UploadError),
#[fail(display = "failed to change file parameters")]
Params(#[cause] ParamsError),
#[fail(display = "failed to set the password")]
Password(#[cause] PasswordError),
}
impl From<MetaError> for Error {
fn from(err: MetaError) -> Error {
Error::Prepare(PrepareError::Meta(err))
}
}
impl From<FileError> for Error {
fn from(err: FileError) -> Error {
Error::File(err)
}
}
impl From<ReaderError> for Error {
fn from(err: ReaderError) -> Error {
Error::Prepare(PrepareError::Reader(err))
}
}
impl From<UploadError> for Error {
fn from(err: UploadError) -> Error {
Error::Upload(err)
}
}
impl From<ParamsError> for Error {
fn from(err: ParamsError) -> Error {
Error::Params(err)
}
}
impl From<PasswordError> for Error {
fn from(err: PasswordError) -> Error {
Error::Password(err)
}
}
#[derive(Fail, Debug)]
pub enum PrepareError {
#[fail(display = "failed to prepare file metadata")]
Meta(#[cause] MetaError),
#[fail(display = "failed to access the file to upload")]
Reader(#[cause] ReaderError),
}
#[derive(Fail, Debug)]
pub enum MetaError {
#[fail(display = "failed to encrypt file metadata")]
Encrypt,
}
#[derive(Fail, Debug)]
pub enum ReaderError {
#[fail(display = "failed to create file encryptor")]
Encrypt,
#[fail(display = "failed to create progress reader")]
Progress,
}
#[derive(Fail, Debug)]
pub enum FileError {
#[fail(display = "the given path is not an existing file")]
NotAFile,
#[fail(display = "failed to open the file to upload")]
Open(#[cause] IoError),
}
#[derive(Fail, Debug)]
pub enum UploadError {
#[fail(display = "failed to update upload progress")]
Progress,
#[fail(display = "failed to request file upload")]
Request,
#[fail(display = "bad response from server while uploading")]
Response(#[cause] ResponseError),
#[fail(display = "failed to decode upload response")]
Decode(#[cause] ReqwestError),
#[fail(display = "failed to parse received URL")]
ParseUrl(#[cause] UrlParseError),
}
impl From<UrlParseError> for UploadError {
fn from(err: UrlParseError) -> UploadError {
UploadError::ParseUrl(err)
}
}