mod settings;
mod protocol;
use anyhow::Result;
use clap::{Parser, Subcommand};
use protocol::BieProtocol;
use std::io::Write;
use std::path::PathBuf;
use tempfile::NamedTempFile;
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::protocol::Message;
use url::Url;
use futures::{SinkExt, StreamExt};
#[derive(Parser)]
#[command(name = "bie")]
#[command(bin_name = "bie")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Get {
file_name: PathBuf,
},
Config,
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let cli = Cli::parse();
match cli.command {
Commands::Config => {
let settings = settings::Settings::load()?;
println!("{:?}", settings);
Ok(())
}
Commands::Get { file_name } => {
let settings = settings::Settings::load()?;
let url = create_websocket_url(&settings.bastion_server_url)?;
let ws_url = url.to_string();
let (ws_stream, _) = connect_async(&ws_url).await?;
let (mut write, mut read) = ws_stream.split();
let token_message = read.next().await;
let token_message =
token_message.ok_or_else(|| anyhow::anyhow!("No token message received"))?;
let token_message = token_message?;
let token = match token_message {
Message::Text(token) => token,
_ => {
return Err(anyhow::anyhow!("Invalid token message"));
}
};
let url = format!("{}/upload/{}", settings.bastion_server_url, token);
println!("In order to send a file, use this snippet:\n");
println!("echo <your-file-name> | xargs -I{{}} curl -v -X POST -F \"file=@{{}}\" {}\n", url);
println!("<type echo then <tab> to get file name autocompletion, then copy snippet from \"|\", or Ctrl-C to exit>");
let mut temp_file = NamedTempFile::new()?;
loop {
let message = read.next().await;
let message = message.ok_or_else(|| anyhow::anyhow!("No message received"))?;
let message = message?;
match message {
Message::Binary(data) => {
match BieProtocol::from(&data[..]) {
BieProtocol::FileChunk(chunk) => {
temp_file.write_all(&chunk)?;
}
BieProtocol::EndOfFile => {
break;
}
_ => {
return Err(anyhow::anyhow!("Invalid message"));
}
}
}
Message::Close(_) => {
return Ok(());
}
_ => {
return Err(anyhow::anyhow!("Invalid message"));
}
}
}
temp_file.flush()?;
temp_file.persist(file_name.as_path())?;
println!("File saved to {}", file_name.display());
write.close().await?;
Ok(())
}
}
}
fn create_websocket_url(server_url: &str) -> Result<Url, anyhow::Error> {
let mut url = Url::parse(server_url)?;
if url.scheme() == "http" {
url.set_scheme("ws")
.map_err(|_| anyhow::anyhow!("Invalid scheme"))?;
} else if url.scheme() == "https" {
url.set_scheme("wss")
.map_err(|_| anyhow::anyhow!("Invalid scheme"))?;
} else {
return Err(anyhow::anyhow!("Invalid scheme"));
}
url.set_path("/wait_file");
Ok(url)
}