yodle/src/client.rs

138 lines
3.9 KiB
Rust

// Copyright (c) 2019 Reyk Floeter <contact@reykfloeter.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
use crate::{server::read_line, Config};
use std::{
io::{Error, ErrorKind, Result},
os::unix::fs::MetadataExt,
path::PathBuf,
};
use tokio::{
fs::{remove_file, File},
io::{copy, split, BufReader, BufWriter},
prelude::*,
};
use tokio_libtls::prelude::*;
pub(crate) async fn run(config: Config, filename: PathBuf) -> Result<()> {
let (cert, key, ca) = config.load_keys()?;
let mut options = config.load_client_options();
let tls_config = TlsConfigBuilder::new()
.ca_file(ca)
.keypair_file(cert, key, None)
.build()
.unwrap();
let addr = config.address.unwrap();
let tls = AsyncTls::connect(&addr.to_string(), &tls_config, options.build())
.await
.unwrap();
let peer_addr: String = addr.to_string();
let (reader, writer) = split(tls);
let mut reader = BufReader::new(reader);
let mut writer = BufWriter::new(writer);
// Send the filename
let name = match filename
.file_name()
.ok_or_else(|| {
debug!("{} failed: file name ({})", peer_addr, filename.display());
Error::new(ErrorKind::Other, "file")
})?
.to_str()
{
Some(name) => name,
None => {
debug!(
"{} failed: filename format ({})",
peer_addr,
filename.display()
);
return Err(Error::new(ErrorKind::Other, "file format"));
}
};
let _ = writer.write_all(format!("{}\n", name).as_bytes()).await;
// Send the file size
let file_size = filename.metadata()?.size();
let _ = writer
.write_all(format!("{}\n", file_size).as_bytes())
.await;
debug!(
"{} status: sending {} ({} bytes)",
peer_addr,
filename.display(),
file_size
);
// Send the actual file
let file = match File::open(&filename).await {
Ok(f) => f,
Err(err) => {
debug!(
"{} failed {}: file ({})",
peer_addr,
filename.display(),
err
);
return Err(err);
}
};
let mut file_reader = file.take(file_size);
let copied = match copy(&mut file_reader, &mut writer).await {
Ok(s) => s,
Err(err) => {
debug!("{} failed: I/O ({:?})", peer_addr, err);
return Err(err);
}
};
if copied != file_size {
drop(file_reader);
let _ = remove_file(&filename).await;
warn!(
"{} failed: {} ({}/{} bytes)",
peer_addr,
filename.display(),
copied,
file_size
);
} else {
info!(
"{} success: {} ({} bytes)",
peer_addr,
filename.display(),
copied
);
}
// Read the server result
match read_line(&peer_addr, &mut reader).await {
Ok(s) if s.starts_with("success") => s,
Ok(s) => {
debug!("server: {}", s);
return Err(Error::new(ErrorKind::Other, s));
}
Err(err) => {
debug!("{}", err);
return Err(err);
}
};
Ok(())
}