extern crate mime;
use std::fs::File;
use std::io::{self, Error as IoError, Read};
#[cfg(feature = "send3")]
use std::mem;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
#[cfg(feature = "send2")]
use self::mime::APPLICATION_OCTET_STREAM;
use mime_guess::{guess_mime_type, Mime};
use openssl::symm::encrypt_aead;
#[cfg(feature = "send2")]
use reqwest::header::AUTHORIZATION;
#[cfg(feature = "send2")]
use reqwest::multipart::{Form, Part};
use reqwest::Error as ReqwestError;
#[cfg(feature = "send2")]
use reqwest::Request;
#[cfg(feature = "send3")]
use serde_json;
use url::{ParseError as UrlParseError, Url};
#[cfg(feature = "send3")]
use websocket::{result::WebSocketError, OwnedMessage};
use super::params::{Error as ParamsError, Params, ParamsData};
use super::password::{Error as PasswordError, Password};
#[cfg(feature = "send2")]
use crate::api::nonce::header_nonce;
#[cfg(feature = "send2")]
use crate::api::request::ensure_success;
use crate::api::request::ResponseError;
use crate::api::Version;
use crate::client::Client;
use crate::crypto::b64;
use crate::crypto::key_set::KeySet;
#[cfg(feature = "send3")]
use crate::file::info::FileInfo;
use crate::file::metadata::Metadata;
use crate::file::remote_file::RemoteFile;
#[cfg(feature = "send3")]
use crate::io::ChunkRead;
#[cfg(feature = "send2")]
use crate::pipe::crypto::GcmCrypt;
#[cfg(feature = "send3")]
use crate::pipe::crypto::{ece, EceCrypt};
use crate::pipe::{
prelude::*,
progress::{ProgressPipe, ProgressReporter},
};
pub struct Upload {
version: Version,
host: Url,
path: PathBuf,
name: Option<String>,
password: Option<String>,
params: Option<ParamsData>,
}
impl Upload {
pub fn new(
version: Version,
host: Url,
path: PathBuf,
name: Option<String>,
password: Option<String>,
params: Option<ParamsData>,
) -> Self {
Self {
version,
host,
path,
name,
password,
params,
}
}
pub fn invoke(
self,
client: &Client,
reporter: Option<&Arc<Mutex<ProgressReporter>>>,
) -> Result<RemoteFile, Error> {
let file = FileData::from(&self.path)?;
let key = KeySet::generate(true);
let reader = self.create_reader(&key, reporter.cloned())?;
let reader_len = reader.len_in() as u64;
if let Some(reporter) = reporter {
reporter
.lock()
.map_err(|_| UploadError::Progress)?
.start(reader_len);
}
let (result, nonce) = match self.version {
#[cfg(feature = "send2")]
Version::V2 => self.upload_send2(client, &key, &file, reader)?,
#[cfg(feature = "send3")]
Version::V3 => self.upload_send3(client, &key, &file, reader)?,
};
if let Some(reporter) = reporter {
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)
}
#[cfg(feature = "send2")]
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_send2(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)
}
#[cfg(feature = "send3")]
fn create_file_info(&self, key: &KeySet, file: &FileData) -> Result<String, MetaError> {
let name = self.name.clone().unwrap_or_else(|| file.name().to_owned());
let mime = format!("{}", file.mime());
let metadata = Metadata::from_send3(name, mime, file.size())
.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(FileInfo::from(None, None, b64::encode(&metadata), key).to_json())
}
fn create_reader(
&self,
key: &KeySet,
reporter: Option<Arc<Mutex<ProgressReporter>>>,
) -> Result<Reader, Error> {
let file = match File::open(self.path.as_path()) {
Ok(file) => file,
Err(err) => return Err(FileError::Open(err).into()),
};
let len = file
.metadata()
.expect("failed to fetch file metadata")
.len();
let progress = ProgressPipe::zero(len, reporter);
let reader = progress.reader(Box::new(file));
match self.version {
#[cfg(feature = "send2")]
Version::V2 => {
let encrypt = GcmCrypt::encrypt(len as usize, key.file_key().unwrap(), key.iv());
let reader = encrypt.reader(Box::new(reader));
Ok(Reader::new(Box::new(reader)))
}
#[cfg(feature = "send3")]
Version::V3 => {
let ikm = key.secret().to_vec();
let encrypt = EceCrypt::encrypt(len as usize, ikm, None);
let reader = encrypt.reader(Box::new(reader));
Ok(Reader::new(Box::new(reader)))
}
}
}
#[cfg(feature = "send2")]
fn upload_send2(
&self,
client: &Client,
key: &KeySet,
file: &FileData,
reader: Reader,
) -> Result<(RemoteFile, Option<Vec<u8>>), Error> {
let metadata = self.create_metadata(&key, file)?;
let req = self.create_request_send2(client, &key, &metadata, reader)?;
self.execute_request_send2(req, client, &key)
.map_err(|e| e.into())
}
#[cfg(feature = "send2")]
fn create_request_send2(
&self,
client: &Client,
key: &KeySet,
metadata: &[u8],
reader: Reader,
) -> Result<Request, UploadError> {
let len = reader.len_out() as u64;
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")?;
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()
.map_err(|_| UploadError::Request)
}
#[cfg(feature = "send2")]
fn execute_request_send2(
&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))
}
#[cfg(feature = "send3")]
fn upload_send3(
&self,
client: &Client,
key: &KeySet,
file_data: &FileData,
mut reader: Reader,
) -> Result<(RemoteFile, Option<Vec<u8>>), Error> {
let ws_url = self
.host
.join("api/ws")
.map_err(|e| Error::Upload(e.into()))?;
let mut client = client
.websocket(ws_url.as_str())
.map_err(|_| Error::Upload(UploadError::Request))?;
let file_info = self
.create_file_info(&key, file_data)
.map_err(|e| -> Error { e.into() })?;
let ws_metadata = OwnedMessage::Text(file_info);
client
.send_message(&ws_metadata)
.map_err(|e| Error::Upload(e.into()))?;
let result = client
.recv_message()
.map_err(|_| Error::Upload(UploadError::InvalidResponse))?;
let upload_response: UploadResponse = match result {
OwnedMessage::Text(ref data) => serde_json::from_str(data)
.map_err(|_| Error::Upload(UploadError::InvalidResponse))?,
_ => return Err(UploadError::InvalidResponse.into()),
};
let mut header = vec![0u8; ece::HEADER_LEN as usize];
reader
.read_exact(&mut header)
.expect("failed to read header from reader");
client
.send_message(&OwnedMessage::Binary(header))
.map_err(|e| Error::Upload(e.into()))?;
let result =
reader
.chunks(ece::RS as usize)
.fold(None, |result: Option<UploadError>, chunk| {
if result.is_some() {
return result;
}
let message = OwnedMessage::Binary(chunk.expect("invalid chunk"));
client.send_message(&message).err().map(|e| e.into())
});
if let Some(err) = result {
return Err(err.into());
}
client
.send_message(&OwnedMessage::Binary(vec![0]))
.map_err(|e| Error::Upload(e.into()))?;
let status = match client
.recv_message()
.map_err(|_| Error::Upload(UploadError::InvalidResponse))?
{
OwnedMessage::Text(status) => Some(status),
_ => None,
};
let ok = status
.and_then(|s| serde_json::from_str::<UploadStatusResponse>(&s).ok())
.map(|s| s.is_ok())
.unwrap_or(false);
if !ok {
return Err(UploadError::Response(ResponseError::Undefined).into());
}
let _ = client.shutdown();
mem::drop(client);
let remote_file = upload_response.into_file(self.host.clone(), &key)?;
Ok((remote_file, None))
}
}
#[derive(Debug, Deserialize)]
struct UploadResponse {
id: String,
url: String,
#[serde(alias = "ownerToken", alias = "owner")]
owner_token: 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_token),
))
}
}
#[derive(Debug, Deserialize)]
#[cfg(feature = "send3")]
struct UploadStatusResponse {
ok: bool,
}
#[cfg(feature = "send3")]
impl UploadStatusResponse {
pub fn is_ok(&self) -> bool {
self.ok
}
}
struct FileData<'a> {
name: &'a str,
mime: Mime,
#[allow(unused)]
size: u64,
}
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",
};
let size = path.metadata()?.len();
Ok(Self {
name,
mime: guess_mime_type(path),
size,
})
}
pub fn name(&self) -> &str {
self.name
}
pub fn mime(&self) -> &Mime {
&self.mime
}
#[cfg(feature = "send3")]
pub fn size(&self) -> u64 {
self.size
}
}
struct Reader {
inner: Box<dyn ReadLen>,
}
impl Reader {
pub fn new(inner: Box<dyn ReadLen>) -> Self {
Self { inner }
}
}
impl Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl PipeLen for Reader {
fn len_in(&self) -> usize {
self.inner.len_in()
}
fn len_out(&self) -> usize {
self.inner.len_out()
}
}
#[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),
#[fail(display = "failed to create uploader client")]
Client,
}
#[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),
}
impl From<IoError> for FileError {
fn from(err: IoError) -> FileError {
FileError::Open(err)
}
}
#[derive(Fail, Debug)]
pub enum UploadError {
#[fail(display = "failed to update upload progress")]
Progress,
#[fail(display = "failed to request file upload")]
Request,
#[fail(display = "failed to stream file for upload over websocket")]
#[cfg(feature = "send3")]
UploadStream(#[cause] WebSocketError),
#[fail(display = "got invalid response from server")]
InvalidResponse,
#[fail(display = "bad response from server for uploading")]
Response(#[cause] ResponseError),
#[fail(display = "failed to decode upload response")]
Decode(#[cause] ReqwestError),
#[fail(display = "failed to parse received URL")]
ParseUrl(#[cause] UrlParseError),
}
#[cfg(feature = "send3")]
impl From<WebSocketError> for UploadError {
fn from(err: WebSocketError) -> UploadError {
UploadError::UploadStream(err)
}
}
impl From<ResponseError> for UploadError {
fn from(err: ResponseError) -> UploadError {
UploadError::Response(err)
}
}
impl From<UrlParseError> for UploadError {
fn from(err: UrlParseError) -> UploadError {
UploadError::ParseUrl(err)
}
}