// Copyright (c) 2019 Reyk Floeter // // 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(()) }