use crate::models::Transaction;
use crate::codec::serialize_transaction;
use crate::constants::permission::{FILE_PERMS_PUBLIC, FILE_PERMS_SECRET};
use crate::crypto::helpers::parse_signing_key_content;
use crate::errors::IoError;
use crate::serde::{parse_lookup_tables, LookupTableEntry};
use crate::layers::io as io_layer;
use crate::models::input_transaction::{InputTransaction, UiTransaction};
use crate::serde::fmt::OutputFormat;
use crate::{Result, ToolError};
use bs58;
use data_encoding::BASE64 as B64;
use ed25519_dalek::SigningKey;
use std::io as std_io;
use std::path::{Path, PathBuf};
enum TextSource<'a> {
Inline(&'a str),
File(&'a str),
Stdin,
}
fn read_input(path: Option<&str>) -> std::result::Result<String, IoError> {
match path {
Some(p) if p != "-" => {
io_layer::read_from_file(Path::new(p)).map_err(|e| IoError::IoWithPath {
source: e,
path: Some(p.to_string()),
})
}
_ => io_layer::read_from_stdin().map_err(|e| IoError::IoWithPath {
source: e,
path: None,
}),
}
}
pub fn read_text_source(
inline: Option<&str>,
file: Option<&str>,
allow_stdin: bool,
) -> Result<String> {
let src = to_text_source(inline, file)?;
resolve_text_source(src, allow_stdin)
}
fn write_output(path: Option<&str>, data: &str, force: bool) -> std::result::Result<(), ToolError> {
let target = match path {
Some(p) if p != "-" => OutputTarget::File(Path::new(p)),
_ => OutputTarget::Stdout,
};
write_bytes_with_opts(&target, data.as_bytes(), FILE_PERMS_PUBLIC, force).map_err(|e| {
match target {
OutputTarget::Stdout => ToolError::Io(IoError::IoWithPath {
source: e,
path: None,
}),
OutputTarget::File(p) => ToolError::Io(IoError::IoWithPath {
source: e,
path: Some(p.display().to_string()),
}),
}
})
}
pub fn read_input_transaction(input: Option<&str>) -> Result<InputTransaction> {
let raw = match input {
Some(p) => read_input(Some(p))?,
None => read_input(None)?,
};
crate::serde::input_tx::parse_input_transaction(Some(&raw)).map_err(ToolError::from)
}
pub fn read_lookup_tables(
path: &str,
) -> Result<LookupTableEntry> {
let raw = read_input(Some(path)).map_err(ToolError::Io)?;
parse_lookup_tables(&raw).map_err(ToolError::from)
}
pub fn read_and_parse_secret_key(path: &str) -> Result<SigningKey> {
let text = read_secret_key_file(path)?;
let key = parse_signing_key_content(&text)?;
Ok(key)
}
pub fn read_secret_key_file(path: &str) -> std::result::Result<String, ToolError> {
if path == "-" {
return Err(ToolError::Io(IoError::IoWithPath {
source: std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"reading secret key from stdin is disabled",
),
path: Some("-".to_string()),
}));
}
let p = Path::new(path);
if !p.exists() {
return Err(ToolError::Io(IoError::IoWithPath {
source: std::io::Error::new(std::io::ErrorKind::NotFound, "secret key file not found"),
path: Some(path.to_string()),
}));
}
if !p.is_file() {
return Err(ToolError::Io(IoError::IoWithPath {
source: std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"secret key path is not a file",
),
path: Some(path.to_string()),
}));
}
let s = read_input(Some(path)).map_err(ToolError::Io)?;
Ok(s.trim().to_string())
}
pub fn write_signed_transaction(
transaction: &Transaction,
format: OutputFormat,
output: Option<&str>,
force: bool,
) -> Result<()> {
match format {
OutputFormat::Json { pretty } => {
let ui = UiTransaction::from(transaction);
let out_str = if pretty {
serde_json::to_string_pretty(&ui).map_err(|e| {
ToolError::InvalidInput(format!("failed to serialize JSON: {e}"))
})?
} else {
serde_json::to_string(&ui).map_err(|e| {
ToolError::InvalidInput(format!("failed to serialize JSON: {e}"))
})?
};
write_output(output, &out_str, force)?;
}
OutputFormat::Base64 => {
let raw = serialize_transaction(transaction);
let out_str = B64.encode(&raw);
write_output(output, &out_str, force)?;
}
OutputFormat::Base58 => {
let raw = serialize_transaction(transaction);
let out_str = bs58::encode(&raw).into_string();
write_output(output, &out_str, force)?;
}
}
Ok(())
}
pub fn resolve_final_path_with_default(
output_path: Option<&str>,
default_filename: &str,
) -> PathBuf {
match output_path {
Some(path_str) => {
let p = Path::new(path_str);
if p.is_dir() {
p.join(default_filename)
} else {
p.to_path_buf()
}
}
None => PathBuf::from(default_filename),
}
}
pub fn save_pretty_json<T: serde::Serialize>(
value: &T,
out_path: Option<&str>,
force: bool,
default_filename: &str,
) -> Result<Option<PathBuf>> {
let json_str = serde_json::to_string_pretty(value)
.map_err(|e| ToolError::InvalidInput(format!("failed to serialize JSON: {e}")))?;
let saved = match out_path {
Some(_) => {
let target = resolve_final_path_with_default(out_path, default_filename);
write_public_file(&target, &json_str, force)?;
Some(target)
}
None => None,
};
Ok(saved)
}
pub fn read_mnemonic(input: &str) -> Result<String> {
let path = match input {
"-" => None,
_ => Some(input),
};
let raw = read_input(path).map_err(ToolError::Io)?;
Ok(raw.split_whitespace().collect::<Vec<_>>().join(" "))
}
pub fn read_passphrase(input: &str) -> Result<String> {
let path = match input {
"-" => None,
_ => Some(input),
};
let raw = read_input(path).map_err(ToolError::Io)?;
Ok(raw.trim_end_matches(['\r', '\n']).to_string())
}
pub fn write_secret_file(
path: &Path,
data: &str,
force: bool,
) -> std::result::Result<(), ToolError> {
if path == Path::new("-") {
return Err(ToolError::Io(IoError::IoWithPath {
source: std_io::Error::new(
std_io::ErrorKind::InvalidInput,
"refusing to write secrets to stdout (-)",
),
path: Some(path.display().to_string()),
}));
}
write_bytes_file_with_opts(path, data.as_bytes(), FILE_PERMS_SECRET, force).map_err(|e| {
if e.kind() == std_io::ErrorKind::AlreadyExists {
ToolError::FileExists {
path: path.display().to_string(),
}
} else {
ToolError::Io(IoError::IoWithPath {
source: e,
path: Some(path.display().to_string()),
})
}
})
}
pub fn write_public_file(
path: &Path,
data: &str,
force: bool,
) -> std::result::Result<(), ToolError> {
write_bytes_file_with_opts(path, data.as_bytes(), FILE_PERMS_PUBLIC, force).map_err(|e| {
ToolError::Io(IoError::IoWithPath {
source: e,
path: Some(path.display().to_string()),
})
})
}
use std::fs::OpenOptions;
use std::io::Write;
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
fn to_text_source<'a>(inline: Option<&'a str>, file: Option<&'a str>) -> Result<TextSource<'a>> {
match (inline, file) {
(Some(s), None) => Ok(TextSource::Inline(s)),
(None, Some("-")) => Ok(TextSource::Stdin),
(None, Some(path)) => Ok(TextSource::File(path)),
(Some(_), Some(_)) => Err(ToolError::InvalidInput(
"provide either inline value or --from-file (not both)".to_string(),
)),
(None, None) => Err(ToolError::InvalidInput(
"missing input: pass inline value or --from-file".to_string(),
)),
}
}
fn resolve_text_source(src: TextSource<'_>, allow_stdin: bool) -> Result<String> {
match src {
TextSource::Inline(s) => Ok(s.to_owned()),
TextSource::File(p) => read_input(Some(p)).map_err(ToolError::Io),
TextSource::Stdin => {
if !allow_stdin {
return Err(ToolError::InvalidInput(
"reading from stdin is disabled".to_string(),
));
}
read_input(None).map_err(ToolError::Io)
}
}
}
pub fn read_message(inline: Option<&str>, file: Option<&str>) -> Result<String> {
let src = to_text_source(inline, file)?;
resolve_text_source(src, true)
}
pub fn read_signature(inline: Option<&str>, file: Option<&str>) -> Result<String> {
let src = to_text_source(inline, file)?;
Ok(resolve_text_source(src, true)?.trim().to_string())
}
pub fn read_pubkey(inline: Option<&str>, file: Option<&str>) -> Result<String> {
let src = to_text_source(inline, file)?;
Ok(resolve_text_source(src, true)?.trim().to_string())
}
enum OutputTarget<'a> {
Stdout,
File(&'a Path),
}
fn write_bytes_with_opts(
target: &OutputTarget,
bytes: &[u8],
perms: u32,
force: bool,
) -> std::result::Result<(), std::io::Error> {
match target {
OutputTarget::Stdout => {
let mut stdout = std_io::stdout();
stdout.write_all(bytes)?;
stdout.flush()?;
Ok(())
}
OutputTarget::File(p) => write_bytes_file_with_opts(p, bytes, perms, force),
}
}
fn write_bytes_file_with_opts(
path: &Path,
bytes: &[u8],
perms: u32,
force: bool,
) -> std::result::Result<(), std::io::Error> {
let mut opts = OpenOptions::new();
opts.write(true);
if force {
opts.create(true).truncate(true);
} else {
opts.create_new(true);
}
#[cfg(unix)]
{
opts.mode(perms);
}
let mut file = opts.open(path)?;
file.write_all(bytes)?;
Ok(())
}