Replace CLI I/O argument duplication with common facilities
- Replace `sq_cli::types::IoArgs` with the more granular `sq_cli::types::FileOrStdin`, `sq_cli::types::FileOrCertStore` and `sq_cli::types::FileOrStdout`. - Replace all generic `input` (describing single files) and `output` arguments with the respective new facilities to share code and not repeat ourselves. - Replace the `open_or_stdin()` function with `FileOrStdin::open()`. - Replace the `create_or_stdout()` function with the private `FileOrStdout::create()`, so that it can not be called directly. - Replace the `emit_unstable_cli_warning()` and `create_or_stdout_unsafe()` functions with `FileOrStdout::create_unsafe()`. - Replace the `create_or_stdout_safe()` function with `FileOrStdout::create_safe()`. - Replace the `create_or_stdout_pgp()` function with `FileOrStdout::create_pgp_safe()`. - Remove the field `unstable_cli_warning_emitted` from `Config`, as it is replaced by the static `UNSTABLE_CLI_WARNING`, which allows for tracking whether a warning has been emitted across several instances of `FileOrStdout`.
This commit is contained in:
parent
8f57c0d9f2
commit
ed6069623b
@ -38,7 +38,7 @@ sequoia-net = { version = "0.27", default-features = false }
|
||||
anyhow = "1.0.18"
|
||||
chrono = "0.4.10"
|
||||
# For an MSRV of 1.63: 4.0.32.
|
||||
clap = { version = "4", features = ["derive", "env", "wrap_help"] }
|
||||
clap = { version = "4", features = ["derive", "env", "string", "wrap_help"] }
|
||||
itertools = "0.10"
|
||||
once_cell = "1.17"
|
||||
sequoia-cert-store = "0.3"
|
||||
@ -53,8 +53,9 @@ terminal_size = "0.2.6"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.18"
|
||||
buffered-reader = { version = "1.0.0", default-features = false, features = ["compression-deflate"] }
|
||||
# For an MSRV of 1.63: 4.0.32
|
||||
clap = { version = "4", features = ["derive", "env", "wrap_help"] }
|
||||
clap = { version = "4", features = ["derive", "env", "string", "wrap_help"] }
|
||||
# For an MSRV of 1.63: 4.0.7.
|
||||
clap_complete = "4"
|
||||
# For an MSRV of 1.63: 0.2.6
|
||||
@ -64,6 +65,7 @@ sequoia-openpgp = { version = "1.13", default-features = false }
|
||||
sequoia-net = { version = "0.27", default-features = false }
|
||||
subplot-build = { version = "0.7.0", optional = true }
|
||||
cfg-if = "1"
|
||||
terminal_size = "0.2.6"
|
||||
|
||||
[dev-dependencies]
|
||||
subplotlib = "0.7.0"
|
||||
|
@ -10,7 +10,6 @@ use sequoia_autocrypt as autocrypt;
|
||||
|
||||
use crate::{
|
||||
Config,
|
||||
open_or_stdin,
|
||||
sq_cli,
|
||||
};
|
||||
|
||||
@ -19,9 +18,9 @@ pub fn dispatch(config: Config, c: &sq_cli::autocrypt::Command) -> Result<()> {
|
||||
|
||||
match &c.subcommand {
|
||||
Decode(command) => {
|
||||
let input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let mut output = config.create_or_stdout_pgp(
|
||||
command.io.output.as_deref(),
|
||||
let input = command.input.open()?;
|
||||
let mut output = command.output.create_pgp_safe(
|
||||
config.force,
|
||||
command.binary,
|
||||
armor::Kind::PublicKey,
|
||||
)?;
|
||||
@ -34,9 +33,8 @@ pub fn dispatch(config: Config, c: &sq_cli::autocrypt::Command) -> Result<()> {
|
||||
output.finalize()?;
|
||||
}
|
||||
EncodeSender(command) => {
|
||||
let input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(command.io.output.as_deref())?;
|
||||
let input = command.input.open()?;
|
||||
let mut output = command.output.create_safe(config.force)?;
|
||||
let cert = Cert::from_reader(input)?;
|
||||
let addr = command.address.clone()
|
||||
.or_else(|| {
|
||||
|
@ -150,8 +150,8 @@ pub fn certify(config: Config, c: certify::Command)
|
||||
|
||||
|
||||
// And export it.
|
||||
let mut message = config.create_or_stdout_pgp(
|
||||
c.output.as_deref(),
|
||||
let mut message = c.output.create_pgp_safe(
|
||||
config.force,
|
||||
c.binary,
|
||||
sequoia_openpgp::armor::Kind::PublicKey,
|
||||
)?;
|
||||
|
@ -13,6 +13,7 @@ use sequoia_cert_store as cert_store;
|
||||
use cert_store::Store;
|
||||
use cert_store::store::UserIDQueryParams;
|
||||
|
||||
use crate::sq_cli::types::FileOrStdout;
|
||||
use crate::{
|
||||
Config,
|
||||
print_error_chain,
|
||||
@ -76,8 +77,12 @@ pub fn dispatch(config: Config, cmd: export::Command) -> Result<()> {
|
||||
return Err(anyhow::anyhow!("Invalid arguments."));
|
||||
}
|
||||
|
||||
let mut sink = config.create_or_stdout_pgp(
|
||||
None, cmd.binary, armor::Kind::PublicKey)?;
|
||||
let output = FileOrStdout::default();
|
||||
let mut sink = output.create_pgp_safe(
|
||||
config.force,
|
||||
cmd.binary,
|
||||
armor::Kind::PublicKey,
|
||||
)?;
|
||||
|
||||
let mut exported_something = false;
|
||||
|
||||
|
@ -12,10 +12,8 @@ use sequoia_cert_store as cert_store;
|
||||
use cert_store::LazyCert;
|
||||
use cert_store::StoreUpdate;
|
||||
|
||||
use crate::{
|
||||
Config,
|
||||
open_or_stdin,
|
||||
};
|
||||
use crate::sq_cli::types::FileOrStdin;
|
||||
use crate::Config;
|
||||
|
||||
use crate::sq_cli::import;
|
||||
|
||||
@ -32,8 +30,7 @@ pub fn dispatch<'store>(mut config: Config<'store>, cmd: import::Command)
|
||||
|
||||
let inner = || -> Result<()> {
|
||||
for input in inputs.into_iter() {
|
||||
let input = open_or_stdin(
|
||||
if input == PathBuf::from("-") { None } else { Some(&input) })?;
|
||||
let input = FileOrStdin::from(input).open()?;
|
||||
let raw_certs = RawCertParser::from_reader(input)?;
|
||||
|
||||
let cert_store = config.cert_store_mut_or_else()?;
|
||||
|
@ -25,26 +25,23 @@ use crate::SECONDS_IN_YEAR;
|
||||
use crate::SECONDS_IN_DAY;
|
||||
|
||||
use crate::sq_cli::inspect;
|
||||
use crate::sq_cli::types::FileOrStdout;
|
||||
|
||||
pub fn inspect(mut config: Config, c: inspect::Command)
|
||||
pub fn inspect(config: Config, c: inspect::Command)
|
||||
-> Result<()>
|
||||
{
|
||||
// sq inspect does not have --output, but commands::inspect does.
|
||||
// Work around this mismatch by always creating a stdout output.
|
||||
let output = &mut config.create_or_stdout_unsafe(None)?;
|
||||
let output_type = FileOrStdout::default();
|
||||
let output = &mut output_type.create_unsafe(config.force)?;
|
||||
|
||||
let policy = &config.policy;
|
||||
let time = Some(config.time);
|
||||
|
||||
let print_certifications = c.certifications;
|
||||
|
||||
let input = c.input.as_deref();
|
||||
let input_name = if let Some(input) = c.input.as_ref() {
|
||||
format!("{}", input.display())
|
||||
} else {
|
||||
"-".to_string()
|
||||
};
|
||||
write!(output, "{}: ", input_name)?;
|
||||
let input = c.input;
|
||||
write!(output, "{}: ", input)?;
|
||||
|
||||
let mut type_called = false; // Did we print the type yet?
|
||||
let mut encrypted = false; // Is it an encrypted message?
|
||||
@ -56,16 +53,16 @@ pub fn inspect(mut config: Config, c: inspect::Command)
|
||||
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
let mut ppr = if c.cert.is_empty() {
|
||||
if let Some(input) = input {
|
||||
if ! input.exists() &&
|
||||
format!("{}", input.display()).parse::<KeyHandle>().is_ok() {
|
||||
if let Some(path) = input.inner() {
|
||||
if ! path.exists() &&
|
||||
format!("{}", input).parse::<KeyHandle>().is_ok() {
|
||||
eprintln!("The file {} does not exist, \
|
||||
did you mean \"sq inspect --cert {}\"?",
|
||||
input.display(), input.display());
|
||||
input, input);
|
||||
}
|
||||
}
|
||||
|
||||
openpgp::parse::PacketParser::from_reader(crate::open_or_stdin(input)?)?
|
||||
openpgp::parse::PacketParser::from_reader(input.open()?)?
|
||||
} else {
|
||||
let cert_store = config.cert_store_or_else()?;
|
||||
for cert in c.cert.into_iter() {
|
||||
@ -174,7 +171,7 @@ pub fn inspect(mut config: Config, c: inspect::Command)
|
||||
writeln!(output, " Cert: {}", is_cert.unwrap_err())?;
|
||||
writeln!(output, " Keyring: {}", is_keyring.unwrap_err())?;
|
||||
writeln!(output)?;
|
||||
writeln!(output, "Hint: Try 'sq packet dump {}'", input_name)?;
|
||||
writeln!(output, "Hint: Try 'sq packet dump {}'", input)?;
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
|
@ -20,12 +20,11 @@ use openpgp::Result;
|
||||
use sequoia_openpgp as openpgp;
|
||||
|
||||
use crate::decrypt_key;
|
||||
use crate::open_or_stdin;
|
||||
use crate::sq_cli;
|
||||
use crate::Config;
|
||||
|
||||
pub fn adopt(config: Config, command: sq_cli::key::AdoptCommand) -> Result<()> {
|
||||
let input = open_or_stdin(command.certificate.as_deref())?;
|
||||
let input = command.certificate.open()?;
|
||||
let cert = Cert::from_reader(input)?;
|
||||
let mut wanted: Vec<(
|
||||
KeyHandle,
|
||||
@ -209,7 +208,7 @@ pub fn adopt(config: Config, command: sq_cli::key::AdoptCommand) -> Result<()> {
|
||||
|
||||
let cert = cert.clone().insert_packets(packets.clone())?;
|
||||
|
||||
let mut sink = config.create_or_stdout_safe(command.output.as_deref())?;
|
||||
let mut sink = command.output.create_safe(config.force)?;
|
||||
if command.binary {
|
||||
cert.as_tsk().serialize(&mut sink)?;
|
||||
} else {
|
||||
|
@ -5,7 +5,6 @@ use openpgp::serialize::Serialize;
|
||||
use sequoia_openpgp as openpgp;
|
||||
|
||||
use crate::decrypt_key;
|
||||
use crate::open_or_stdin;
|
||||
use crate::sq_cli;
|
||||
use crate::Config;
|
||||
|
||||
@ -16,7 +15,7 @@ pub fn attest_certifications(
|
||||
// Attest to all certifications?
|
||||
let all = !command.none; // All is the default.
|
||||
|
||||
let input = open_or_stdin(command.key.as_deref())?;
|
||||
let input = command.key.open()?;
|
||||
let key = Cert::from_reader(input)?;
|
||||
|
||||
// Get a signer.
|
||||
@ -63,7 +62,7 @@ pub fn attest_certifications(
|
||||
// Finally, add the new signatures.
|
||||
let key = key.insert_packets(attestation_signatures)?;
|
||||
|
||||
let mut sink = config.create_or_stdout_safe(command.output.as_deref())?;
|
||||
let mut sink = command.output.create_safe(config.force)?;
|
||||
if command.binary {
|
||||
key.as_tsk().serialize(&mut sink)?;
|
||||
} else {
|
||||
|
@ -4,7 +4,6 @@ use openpgp::Cert;
|
||||
use openpgp::Result;
|
||||
use sequoia_openpgp as openpgp;
|
||||
|
||||
use crate::open_or_stdin;
|
||||
use crate::sq_cli;
|
||||
use crate::Config;
|
||||
|
||||
@ -12,9 +11,8 @@ pub fn extract_cert(
|
||||
config: Config,
|
||||
command: sq_cli::key::ExtractCertCommand,
|
||||
) -> Result<()> {
|
||||
let input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(command.io.output.as_deref())?;
|
||||
let input = command.input.open()?;
|
||||
let mut output = command.output.create_safe(config.force)?;
|
||||
|
||||
let cert = Cert::from_reader(input)?;
|
||||
if command.binary {
|
||||
|
@ -14,6 +14,7 @@ use sequoia_openpgp as openpgp;
|
||||
|
||||
use crate::sq_cli;
|
||||
use crate::Config;
|
||||
use crate::sq_cli::types::FileOrStdout;
|
||||
|
||||
pub fn generate(
|
||||
config: Config,
|
||||
@ -123,7 +124,7 @@ pub fn generate(
|
||||
let (cert, rev) = builder.generate()?;
|
||||
|
||||
// Export
|
||||
if let Some(key_path) = command.export.as_ref() {
|
||||
if let Some(key_path) = command.export {
|
||||
if &format!("{}", key_path.display()) == "-"
|
||||
&& command.rev_cert.is_none()
|
||||
{
|
||||
@ -132,10 +133,12 @@ pub fn generate(
|
||||
))
|
||||
}
|
||||
|
||||
let key_path = FileOrStdout::from(key_path);
|
||||
|
||||
let rev_path = if command.rev_cert.is_some() {
|
||||
command.rev_cert
|
||||
FileOrStdout::new(command.rev_cert)
|
||||
} else {
|
||||
Some(PathBuf::from(format!("{}.rev", key_path.display())))
|
||||
FileOrStdout::from(PathBuf::from(format!("{}.rev", key_path)))
|
||||
};
|
||||
|
||||
let headers = cert.armor_headers();
|
||||
@ -147,7 +150,7 @@ pub fn generate(
|
||||
.map(|value| ("Comment", value.as_str()))
|
||||
.collect();
|
||||
|
||||
let w = config.create_or_stdout_safe(Some(key_path))?;
|
||||
let w = key_path.create_safe(config.force)?;
|
||||
let mut w = Writer::with_headers(w, Kind::SecretKey, headers)?;
|
||||
cert.as_tsk().serialize(&mut w)?;
|
||||
w.finalize()?;
|
||||
@ -161,7 +164,7 @@ pub fn generate(
|
||||
.collect();
|
||||
headers.insert(0, ("Comment", "Revocation certificate for"));
|
||||
|
||||
let w = config.create_or_stdout_safe(rev_path.as_deref())?;
|
||||
let w = rev_path.create_safe(config.force)?;
|
||||
let mut w = Writer::with_headers(w, Kind::Signature, headers)?;
|
||||
Packet::Signature(rev).serialize(&mut w)?;
|
||||
w.finalize()?;
|
||||
|
@ -8,7 +8,6 @@ use openpgp::Result;
|
||||
use sequoia_openpgp as openpgp;
|
||||
|
||||
use crate::decrypt_key;
|
||||
use crate::open_or_stdin;
|
||||
use crate::sq_cli;
|
||||
use crate::Config;
|
||||
|
||||
@ -16,7 +15,7 @@ pub fn password(
|
||||
config: Config,
|
||||
command: sq_cli::key::PasswordCommand,
|
||||
) -> Result<()> {
|
||||
let input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let input = command.input.open()?;
|
||||
let key = Cert::from_reader(input)?;
|
||||
|
||||
if !key.is_tsk() {
|
||||
@ -82,8 +81,7 @@ pub fn password(
|
||||
key = key.insert_packets(encrypted)?;
|
||||
}
|
||||
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(command.io.output.as_deref())?;
|
||||
let mut output = command.output.create_safe(config.force)?;
|
||||
if command.binary {
|
||||
key.as_tsk().serialize(&mut output)?;
|
||||
} else {
|
||||
|
@ -9,7 +9,6 @@ use openpgp::Cert;
|
||||
use openpgp::Result;
|
||||
use sequoia_openpgp as openpgp;
|
||||
|
||||
use crate::open_or_stdin;
|
||||
use crate::sq_cli::key::EncryptPurpose;
|
||||
use crate::sq_cli::key::SubkeyCommand;
|
||||
use crate::sq_cli::key::SubkeyAddCommand;
|
||||
@ -32,7 +31,7 @@ fn subkey_add(
|
||||
config: Config,
|
||||
command: SubkeyAddCommand,
|
||||
) -> Result<()> {
|
||||
let input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let input = command.input.open()?;
|
||||
let cert = Cert::from_reader(input)?;
|
||||
let valid_cert = cert.with_policy(&config.policy, config.time)?;
|
||||
|
||||
@ -59,8 +58,7 @@ fn subkey_add(
|
||||
.set_key_validity_period(validity)?
|
||||
.attach_cert()?;
|
||||
|
||||
let mut sink =
|
||||
config.create_or_stdout_safe(command.io.output.as_deref())?;
|
||||
let mut sink = command.output.create_safe(config.force)?;
|
||||
if command.binary {
|
||||
new_cert.as_tsk().serialize(&mut sink)?;
|
||||
} else {
|
||||
|
@ -19,7 +19,6 @@ use openpgp::Result;
|
||||
use sequoia_openpgp as openpgp;
|
||||
|
||||
use crate::commands::get_primary_keys;
|
||||
use crate::open_or_stdin;
|
||||
use crate::sq_cli;
|
||||
use crate::Config;
|
||||
|
||||
@ -39,7 +38,7 @@ fn userid_add(
|
||||
config: Config,
|
||||
command: sq_cli::key::UseridAddCommand,
|
||||
) -> Result<()> {
|
||||
let input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let input = command.input.open()?;
|
||||
let key = Cert::from_reader(input)?;
|
||||
|
||||
// Fail if any of the User IDs to add already exist in the ValidCert
|
||||
@ -183,8 +182,7 @@ fn userid_add(
|
||||
// Merge additional User IDs into key
|
||||
let cert = key.insert_packets(add)?;
|
||||
|
||||
let mut sink =
|
||||
config.create_or_stdout_safe(command.io.output.as_deref())?;
|
||||
let mut sink = command.output.create_safe(config.force)?;
|
||||
if command.binary {
|
||||
cert.as_tsk().serialize(&mut sink)?;
|
||||
} else {
|
||||
@ -197,7 +195,7 @@ fn userid_strip(
|
||||
config: Config,
|
||||
command: sq_cli::key::UseridStripCommand,
|
||||
) -> Result<()> {
|
||||
let input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let input = command.input.open()?;
|
||||
let key = Cert::from_reader(input)?;
|
||||
|
||||
let orig_cert_valid = key.with_policy(&config.policy, None).is_ok();
|
||||
@ -238,8 +236,7 @@ signatures on other User IDs to make the key valid again.",
|
||||
}
|
||||
}
|
||||
|
||||
let mut sink =
|
||||
config.create_or_stdout_safe(command.io.output.as_deref())?;
|
||||
let mut sink = command.output.create_safe(config.force)?;
|
||||
if command.binary {
|
||||
cert.as_tsk().serialize(&mut sink)?;
|
||||
} else {
|
||||
|
@ -30,7 +30,6 @@ use openpgp::{
|
||||
use crate::{
|
||||
Config,
|
||||
Model,
|
||||
open_or_stdin,
|
||||
output::KeyringListItem,
|
||||
};
|
||||
|
||||
@ -138,10 +137,11 @@ pub fn dispatch(config: Config, c: keyring::Command) -> Result<()> {
|
||||
// better to use Kind::SecretKey here. However, this
|
||||
// requires buffering all certs, which has its own
|
||||
// problems.
|
||||
let mut output =
|
||||
config.create_or_stdout_pgp(command.output.as_deref(),
|
||||
command.binary,
|
||||
armor::Kind::PublicKey)?;
|
||||
let mut output = command.output.create_pgp_safe(
|
||||
config.force,
|
||||
command.binary,
|
||||
armor::Kind::PublicKey,
|
||||
)?;
|
||||
filter(&command.input, &mut output, filter_fn, to_certificate)?;
|
||||
output.finalize()
|
||||
},
|
||||
@ -151,35 +151,38 @@ pub fn dispatch(config: Config, c: keyring::Command) -> Result<()> {
|
||||
// better to use Kind::SecretKey here. However, this
|
||||
// requires buffering all certs, which has its own
|
||||
// problems.
|
||||
let mut output =
|
||||
config.create_or_stdout_pgp(c.output.as_deref(),
|
||||
c.binary,
|
||||
armor::Kind::PublicKey)?;
|
||||
let mut output = c.output.create_pgp_safe(
|
||||
config.force,
|
||||
c.binary,
|
||||
armor::Kind::PublicKey,
|
||||
)?;
|
||||
filter(&c.input, &mut output, Some, false)?;
|
||||
output.finalize()
|
||||
},
|
||||
Merge(c) => {
|
||||
let mut output =
|
||||
config.create_or_stdout_pgp(c.output.as_deref(),
|
||||
c.binary,
|
||||
armor::Kind::PublicKey)?;
|
||||
let mut output = c.output.create_pgp_safe(
|
||||
config.force,
|
||||
c.binary,
|
||||
armor::Kind::PublicKey,
|
||||
)?;
|
||||
merge(&c.input, &mut output)?;
|
||||
output.finalize()
|
||||
},
|
||||
List(c) => {
|
||||
let mut input = open_or_stdin(c.input.as_deref())?;
|
||||
let mut input = c.input.open()?;
|
||||
list(config, &mut input, c.all_userids)
|
||||
},
|
||||
Split(c) => {
|
||||
let mut input = open_or_stdin(c.input.as_deref())?;
|
||||
let mut input = c.input.open()?;
|
||||
let prefix =
|
||||
// The prefix is either specified explicitly...
|
||||
c.prefix.unwrap_or(
|
||||
// ... or we derive it from the input file...
|
||||
c.input.and_then(|i| {
|
||||
let p = PathBuf::from(i);
|
||||
c.input.and_then(|x| {
|
||||
// (but only use the filename)
|
||||
p.file_name().map(|f| String::from(f.to_string_lossy()))
|
||||
x.file_name().map(|f|
|
||||
String::from(f.to_string_lossy())
|
||||
)
|
||||
})
|
||||
// ... or we use a generic prefix...
|
||||
.unwrap_or_else(|| String::from("output"))
|
||||
|
@ -4,7 +4,6 @@ use std::cmp::Ordering;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use sequoia_net::pks;
|
||||
@ -39,6 +38,7 @@ use cert_store::Store;
|
||||
|
||||
use sequoia_wot::store::Store as _;
|
||||
|
||||
use crate::sq_cli::types::FileOrStdout;
|
||||
use crate::{
|
||||
Config,
|
||||
};
|
||||
@ -946,25 +946,20 @@ pub fn join(config: Config, c: packet::JoinCommand) -> Result<()> {
|
||||
let kind = c.kind.into();
|
||||
let output = c.output;
|
||||
let mut sink = if c.binary {
|
||||
// TODO: Does this mean kind is silently ignored if binary is given?
|
||||
// No need for any auto-detection.
|
||||
Some(config.create_or_stdout_pgp(output.as_deref(),
|
||||
true, // Binary.
|
||||
armor::Kind::File)?)
|
||||
} else if let Some(kind) = kind {
|
||||
Some(config.create_or_stdout_pgp(output.as_deref(),
|
||||
false, // Armored.
|
||||
kind)?)
|
||||
} else {
|
||||
None // Defer.
|
||||
};
|
||||
// No need for any auto-detection.
|
||||
Some(output.create_pgp_safe(config.force, true, armor::Kind::File)?)
|
||||
} else if let Some(kind) = kind {
|
||||
Some(output.create_pgp_safe(config.force, false, kind)?)
|
||||
} else {
|
||||
None // Defer.
|
||||
};
|
||||
|
||||
/// Writes a bit-accurate copy of all top-level packets in PPR to
|
||||
/// OUTPUT.
|
||||
fn copy(config: &Config,
|
||||
fn copy<'a, 'b>(config: &Config,
|
||||
mut ppr: PacketParserResult,
|
||||
output: Option<&Path>,
|
||||
sink: &mut Option<Message>)
|
||||
output: &'a FileOrStdout,
|
||||
sink: &'b mut Option<Message<'a>>)
|
||||
-> Result<()> {
|
||||
while let PacketParserResult::Some(pp) = ppr {
|
||||
if sink.is_none() {
|
||||
@ -978,9 +973,9 @@ pub fn join(config: Config, c: packet::JoinCommand) -> Result<()> {
|
||||
_ => armor::Kind::File,
|
||||
};
|
||||
|
||||
*sink = Some(config.create_or_stdout_pgp(output,
|
||||
false, // Armored.
|
||||
kind)?);
|
||||
*sink = Some(
|
||||
output.create_pgp_safe(config.force, false, kind)?
|
||||
);
|
||||
}
|
||||
|
||||
// We (ab)use the mapping feature to create byte-accurate
|
||||
@ -1000,13 +995,13 @@ pub fn join(config: Config, c: packet::JoinCommand) -> Result<()> {
|
||||
let ppr =
|
||||
openpgp::parse::PacketParserBuilder::from_file(name)?
|
||||
.map(true).build()?;
|
||||
copy(&config, ppr, output.as_deref(), &mut sink)?;
|
||||
copy(&config, ppr, &output, &mut sink)?;
|
||||
}
|
||||
} else {
|
||||
let ppr =
|
||||
openpgp::parse::PacketParserBuilder::from_reader(io::stdin())?
|
||||
.map(true).build()?;
|
||||
copy(&config, ppr, output.as_deref(), &mut sink)?;
|
||||
copy(&config, ppr, &output, &mut sink)?;
|
||||
}
|
||||
|
||||
sink.unwrap().finalize()?;
|
||||
|
@ -42,13 +42,13 @@ use crate::{
|
||||
},
|
||||
Config,
|
||||
Model,
|
||||
open_or_stdin,
|
||||
serialize_keyring,
|
||||
output::WkdUrlVariant,
|
||||
print_error_chain,
|
||||
};
|
||||
|
||||
use crate::sq_cli;
|
||||
use crate::sq_cli::types::FileOrStdout;
|
||||
|
||||
const NP: NullPolicy = NullPolicy::new();
|
||||
|
||||
@ -409,9 +409,9 @@ pub fn dispatch_keyserver(mut config: Config, c: sq_cli::keyserver::Command)
|
||||
let cert = rt.block_on(ks.get(handle))
|
||||
.context("Failed to retrieve cert")?;
|
||||
|
||||
if let Some(output) = c.output {
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(Some(&output))?;
|
||||
if c.output.path().is_some() {
|
||||
let mut output = FileOrStdout::from(c.output)
|
||||
.create_safe(config.force)?;
|
||||
if !c.binary {
|
||||
cert.armored().serialize(&mut output)
|
||||
} else {
|
||||
@ -432,9 +432,9 @@ pub fn dispatch_keyserver(mut config: Config, c: sq_cli::keyserver::Command)
|
||||
let certs = rt.block_on(ks.search(addr))
|
||||
.context("Failed to retrieve certs")?;
|
||||
|
||||
if let Some(output) = c.output {
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(Some(&output))?;
|
||||
if c.output.path().is_some() {
|
||||
let mut output = FileOrStdout::from(c.output)
|
||||
.create_safe(config.force)?;
|
||||
serialize_keyring(&mut output, &certs, c.binary)?;
|
||||
} else {
|
||||
let certs = if let Some((ca_filename, ca_userid)) = ca() {
|
||||
@ -454,7 +454,7 @@ pub fn dispatch_keyserver(mut config: Config, c: sq_cli::keyserver::Command)
|
||||
}
|
||||
},
|
||||
Send(c) => {
|
||||
let mut input = open_or_stdin(c.input.as_deref())?;
|
||||
let mut input = c.input.open()?;
|
||||
let cert = Cert::from_reader(&mut input).
|
||||
context("Malformed key")?;
|
||||
|
||||
@ -514,9 +514,9 @@ pub fn dispatch_wkd(mut config: Config, c: sq_cli::wkd::Command) -> Result<()> {
|
||||
// ```
|
||||
// But to keep the parallelism with `store export` and `keyserver get`,
|
||||
// The output is armored if not `--binary` option is given.
|
||||
if let Some(output) = c.output {
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(Some(&output))?;
|
||||
if c.output.path().is_some() {
|
||||
let mut output = FileOrStdout::from(c.output)
|
||||
.create_safe(config.force)?;
|
||||
serialize_keyring(&mut output, &certs, c.binary)?;
|
||||
} else {
|
||||
let certs = certify_downloads(
|
||||
@ -528,7 +528,7 @@ pub fn dispatch_wkd(mut config: Config, c: sq_cli::wkd::Command) -> Result<()> {
|
||||
Generate(c) => {
|
||||
let domain = c.domain;
|
||||
let skip = c.skip;
|
||||
let f = open_or_stdin(c.input.as_deref())?;
|
||||
let f = c.input.open()?;
|
||||
let base_path = c.base_directory;
|
||||
let variant = if c.direct_method {
|
||||
wkd::Variant::Direct
|
||||
@ -587,9 +587,9 @@ pub fn dispatch_dane(mut config: Config, c: sq_cli::dane::Command) -> Result<()>
|
||||
// Because it might be created a WkdServer struct, not
|
||||
// doing it for now.
|
||||
let certs = rt.block_on(dane::get(&email_address))?;
|
||||
if let Some(output) = c.output {
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(Some(&output))?;
|
||||
if c.output.path().is_some() {
|
||||
let mut output = FileOrStdout::from(c.output)
|
||||
.create_safe(config.force)?;
|
||||
serialize_keyring(&mut output, &certs, c.binary)?;
|
||||
} else {
|
||||
let certs = certify_downloads(
|
||||
|
@ -15,11 +15,12 @@ use openpgp::Result;
|
||||
use openpgp::serialize::Serialize;
|
||||
use openpgp::types::KeyFlags;
|
||||
use openpgp::types::ReasonForRevocation;
|
||||
use crate::sq_cli::types::FileOrStdin;
|
||||
use crate::sq_cli::types::FileOrStdout;
|
||||
use crate::{
|
||||
commands::cert_stub,
|
||||
Config,
|
||||
load_certs,
|
||||
open_or_stdin,
|
||||
parse_notations,
|
||||
};
|
||||
|
||||
@ -147,7 +148,7 @@ pub fn revoke_userid(config: Config, c: revoke::UseridCommand) -> Result<()> {
|
||||
|
||||
/// Parse the cert from input and ensure it is only one cert.
|
||||
fn read_cert(input: Option<&Path>) -> Result<Cert> {
|
||||
let input = open_or_stdin(input)?;
|
||||
let input = FileOrStdin::from(input).open()?;
|
||||
|
||||
let cert = CertParser::from_reader(input)?.collect::<Vec<_>>();
|
||||
let cert = match cert.len() {
|
||||
@ -182,7 +183,8 @@ fn revoke(config: Config,
|
||||
notations: &[(bool, NotationData)])
|
||||
-> Result<()>
|
||||
{
|
||||
let mut output = config.create_or_stdout_safe(None)?;
|
||||
let output_type = FileOrStdout::default();
|
||||
let mut output = output_type.create_safe(config.force)?;
|
||||
|
||||
let (secret, mut signer) = if let Some(secret) = secret.as_ref() {
|
||||
if let Ok(keys) = super::get_certification_keys(
|
||||
|
@ -1,7 +1,6 @@
|
||||
use anyhow::Context as _;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::time::SystemTime;
|
||||
use tempfile::NamedTempFile;
|
||||
@ -20,6 +19,7 @@ use openpgp::serialize::stream::{
|
||||
Message, Armorer, Signer, LiteralWriter,
|
||||
};
|
||||
use openpgp::types::SignatureType;
|
||||
use crate::sq_cli::types::FileOrStdout;
|
||||
use crate::{
|
||||
Config,
|
||||
};
|
||||
@ -28,7 +28,7 @@ pub struct SignOpts<'a, 'certdb> {
|
||||
pub config: Config<'certdb>,
|
||||
pub private_key_store: Option<&'a str>,
|
||||
pub input: &'a mut (dyn io::Read + Sync + Send),
|
||||
pub output_path: Option<&'a Path>,
|
||||
pub output_path: &'a FileOrStdout,
|
||||
pub secrets: Vec<openpgp::Cert>,
|
||||
pub detached: bool,
|
||||
pub binary: bool,
|
||||
@ -61,11 +61,12 @@ fn sign_data(opts: SignOpts) -> Result<()> {
|
||||
notations, ..} = opts;
|
||||
let (mut output, prepend_sigs, tmp_path):
|
||||
(Box<dyn io::Write + Sync + Send>, Vec<Signature>, Option<PathBuf>) =
|
||||
if detached && append && output_path.is_some() {
|
||||
if detached && append && output_path.path().is_some() {
|
||||
let output_path = output_path.path().unwrap();
|
||||
// First, read the existing signatures.
|
||||
let mut sigs = Vec::new();
|
||||
let mut ppr =
|
||||
openpgp::parse::PacketParser::from_file(output_path.unwrap())?;
|
||||
openpgp::parse::PacketParser::from_file(output_path)?;
|
||||
|
||||
while let PacketParserResult::Some(pp) = ppr {
|
||||
let (packet, ppr_tmp) = pp.recurse()?;
|
||||
@ -84,12 +85,12 @@ fn sign_data(opts: SignOpts) -> Result<()> {
|
||||
// successful with adding our signature(s), we rename the
|
||||
// file replacing the old one.
|
||||
let tmp_file = NamedTempFile::new_in(
|
||||
PathBuf::from(output_path.unwrap()).parent()
|
||||
PathBuf::from(output_path).parent()
|
||||
.unwrap_or(&PathBuf::from(".")))?;
|
||||
let tmp_path = tmp_file.path().into();
|
||||
(Box::new(tmp_file), sigs, Some(tmp_path))
|
||||
} else {
|
||||
(config.create_or_stdout_safe(output_path)?, Vec::new(), None)
|
||||
(output_path.create_safe(config.force)?, Vec::new(), None)
|
||||
};
|
||||
|
||||
let mut keypairs = super::get_signing_keys(
|
||||
@ -159,17 +160,20 @@ fn sign_data(opts: SignOpts) -> Result<()> {
|
||||
|
||||
if let Some(path) = tmp_path {
|
||||
// Atomically replace the old file.
|
||||
fs::rename(path,
|
||||
output_path.expect("must be Some if tmp_path is Some"))?;
|
||||
fs::rename(
|
||||
path,
|
||||
output_path.path().expect("must be Some if tmp_path is Some"),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sign_message(opts: SignOpts) -> Result<()> {
|
||||
let mut output =
|
||||
opts.config.create_or_stdout_pgp(opts.output_path,
|
||||
opts.binary,
|
||||
armor::Kind::Message)?;
|
||||
let mut output = opts.output_path.create_pgp_safe(
|
||||
opts.config.force,
|
||||
opts.binary,
|
||||
armor::Kind::Message,
|
||||
)?;
|
||||
sign_message_(opts, &mut output)?;
|
||||
output.finalize()?;
|
||||
Ok(())
|
||||
|
180
src/sq.rs
180
src/sq.rs
@ -7,10 +7,10 @@
|
||||
#![doc = include_str!(concat!(env!("OUT_DIR"), "/sq-usage.md"))]
|
||||
|
||||
use anyhow::Context as _;
|
||||
use sq_cli::types::FileOrStdin;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::borrow::Cow;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -21,7 +21,7 @@ use once_cell::unsync::OnceCell;
|
||||
|
||||
use terminal_size::terminal_size;
|
||||
|
||||
use buffered_reader::{BufferedReader, Dup, File, Generic, Limitor};
|
||||
use buffered_reader::{BufferedReader, Dup, File, Limitor};
|
||||
use sequoia_openpgp as openpgp;
|
||||
|
||||
use openpgp::{
|
||||
@ -36,7 +36,7 @@ use openpgp::packet::prelude::*;
|
||||
use openpgp::parse::{Parse, PacketParser, PacketParserResult};
|
||||
use openpgp::packet::signature::subpacket::NotationData;
|
||||
use openpgp::packet::signature::subpacket::NotationDataFlags;
|
||||
use openpgp::serialize::{Serialize, stream::{Message, Armorer}};
|
||||
use openpgp::serialize::Serialize;
|
||||
use openpgp::cert::prelude::*;
|
||||
use openpgp::policy::StandardPolicy as P;
|
||||
use openpgp::serialize::SerializeInto;
|
||||
@ -70,17 +70,6 @@ mod commands;
|
||||
pub mod output;
|
||||
pub use output::{wkd::WkdUrlVariant, Model, OutputFormat, OutputVersion};
|
||||
|
||||
|
||||
fn open_or_stdin(f: Option<&Path>)
|
||||
-> Result<Box<dyn BufferedReader<()>>> {
|
||||
match f {
|
||||
Some(f) => Ok(Box::new(
|
||||
File::open(f)
|
||||
.with_context(|| format!("Failed to open {}", f.display()))?)),
|
||||
None => Ok(Box::new(Generic::new(io::stdin(), None))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads one TSK from every given file.
|
||||
fn load_keys<'a, I>(files: I) -> openpgp::Result<Vec<Cert>>
|
||||
where I: Iterator<Item=&'a Path>
|
||||
@ -250,37 +239,12 @@ fn help_warning(arg: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a warning if sq is run in a non-interactive setting without
|
||||
/// a terminal.
|
||||
///
|
||||
/// Detecting non-interactive use is done using a heuristic.
|
||||
fn emit_unstable_cli_warning() {
|
||||
if terminal_size().is_some() {
|
||||
// stdout is connected to a terminal, assume interactive use.
|
||||
return;
|
||||
}
|
||||
|
||||
// For bash shells, we can use a very simple heuristic. We simply
|
||||
// look at whether the COLUMNS variable is defined in our
|
||||
// environment.
|
||||
if std::env::var_os("COLUMNS").is_some() {
|
||||
// Heuristic detected interactive use.
|
||||
return;
|
||||
}
|
||||
|
||||
eprintln!("\nWARNING: sq does not have a stable CLI interface. \
|
||||
Use with caution in scripts.\n");
|
||||
}
|
||||
|
||||
pub struct Config<'a> {
|
||||
force: bool,
|
||||
output_format: OutputFormat,
|
||||
output_version: Option<OutputVersion>,
|
||||
policy: P<'a>,
|
||||
time: SystemTime,
|
||||
/// Have we emitted the warning yet?
|
||||
unstable_cli_warning_emitted: bool,
|
||||
|
||||
// --no-cert-store
|
||||
no_rw_cert_store: bool,
|
||||
cert_store_path: Option<PathBuf>,
|
||||
@ -297,73 +261,6 @@ pub struct Config<'a> {
|
||||
}
|
||||
|
||||
impl<'store> Config<'store> {
|
||||
/// Opens the file (or stdout) for writing data that is safe for
|
||||
/// non-interactive use.
|
||||
///
|
||||
/// This is suitable for any kind of OpenPGP data, or decrypted or
|
||||
/// authenticated payloads.
|
||||
fn create_or_stdout_safe(&self, f: Option<&Path>)
|
||||
-> Result<Box<dyn io::Write + Sync + Send>> {
|
||||
Config::create_or_stdout(f, self.force)
|
||||
}
|
||||
|
||||
/// Opens the file (or stdout) for writing data that is NOT safe
|
||||
/// for non-interactive use.
|
||||
///
|
||||
/// If our heuristic detects non-interactive use, we will emit a
|
||||
/// warning.
|
||||
fn create_or_stdout_unsafe(&mut self, f: Option<&Path>)
|
||||
-> Result<Box<dyn io::Write + Sync + Send>> {
|
||||
if ! self.unstable_cli_warning_emitted {
|
||||
emit_unstable_cli_warning();
|
||||
self.unstable_cli_warning_emitted = true;
|
||||
}
|
||||
Config::create_or_stdout(f, self.force)
|
||||
}
|
||||
|
||||
/// Opens the file (or stdout) for writing data that is safe for
|
||||
/// non-interactive use because it is an OpenPGP data stream.
|
||||
fn create_or_stdout_pgp<'a>(&self, f: Option<&Path>,
|
||||
binary: bool, kind: armor::Kind)
|
||||
-> Result<Message<'a>> {
|
||||
let sink = self.create_or_stdout_safe(f)?;
|
||||
let mut message = Message::new(sink);
|
||||
if ! binary {
|
||||
message = Armorer::new(message).kind(kind).build()?;
|
||||
}
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Helper function, do not use directly. Instead, use create_or_stdout_safe
|
||||
/// or create_or_stdout_unsafe.
|
||||
fn create_or_stdout(
|
||||
f: Option<&Path>,
|
||||
force: bool,
|
||||
) -> Result<Box<dyn io::Write + Sync + Send>> {
|
||||
match f {
|
||||
None => Ok(Box::new(io::stdout())),
|
||||
Some(p) if p == Path::new("-") => Ok(Box::new(io::stdout())),
|
||||
Some(f) => {
|
||||
if !f.exists() || force {
|
||||
Ok(Box::new(
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.open(f)
|
||||
.context("Failed to create output file")?,
|
||||
))
|
||||
} else {
|
||||
Err(anyhow::anyhow!(format!(
|
||||
"File {:?} exists, use \"sq --force ...\" to \
|
||||
overwrite",
|
||||
f.display()
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cert store's base directory, if it is enabled.
|
||||
fn cert_store_base(&self) -> Option<PathBuf> {
|
||||
if self.no_rw_cert_store {
|
||||
@ -1118,13 +1015,12 @@ fn main() -> Result<()> {
|
||||
None
|
||||
};
|
||||
|
||||
let mut config = Config {
|
||||
let config = Config {
|
||||
force,
|
||||
output_format,
|
||||
output_version,
|
||||
policy: policy.clone(),
|
||||
time,
|
||||
unstable_cli_warning_emitted: false,
|
||||
no_rw_cert_store: c.no_cert_store,
|
||||
cert_store_path: c.cert_store.clone(),
|
||||
pep_cert_store_path: c.pep_cert_store.clone(),
|
||||
@ -1147,9 +1043,8 @@ fn main() -> Result<()> {
|
||||
|
||||
SqSubcommands::Decrypt(command) => {
|
||||
|
||||
let mut input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(command.io.output.as_deref())?;
|
||||
let mut input = command.input.open()?;
|
||||
let mut output = command.output.create_safe(config.force)?;
|
||||
|
||||
let certs = load_certs(
|
||||
command.sender_cert_file.iter().map(|s| s.as_ref()),
|
||||
@ -1197,10 +1092,10 @@ fn main() -> Result<()> {
|
||||
recipients.extend(
|
||||
config.lookup_by_userid(&command.recipients_userid, false)
|
||||
.context("--recipient-userid")?);
|
||||
let mut input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let mut input = command.input.open()?;
|
||||
|
||||
let output = config.create_or_stdout_pgp(
|
||||
command.io.output.as_deref(),
|
||||
let output = command.output.create_pgp_safe(
|
||||
config.force,
|
||||
command.binary,
|
||||
armor::Kind::Message,
|
||||
)?;
|
||||
@ -1224,8 +1119,8 @@ fn main() -> Result<()> {
|
||||
})?;
|
||||
},
|
||||
SqSubcommands::Sign(command) => {
|
||||
let mut input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let output = command.io.output.as_deref();
|
||||
let mut input = command.input.open()?;
|
||||
let output = &command.output;
|
||||
let detached = command.detached;
|
||||
let binary = command.binary;
|
||||
let append = command.append;
|
||||
@ -1238,12 +1133,16 @@ fn main() -> Result<()> {
|
||||
let notations = parse_notations(command.notation)?;
|
||||
|
||||
if let Some(merge) = command.merge {
|
||||
let output = config.create_or_stdout_pgp(output, binary,
|
||||
armor::Kind::Message)?;
|
||||
let mut input2 = open_or_stdin(Some(&merge))?;
|
||||
let output = output.create_pgp_safe(
|
||||
config.force,
|
||||
binary,
|
||||
armor::Kind::Message,
|
||||
)?;
|
||||
let data: FileOrStdin = merge.into();
|
||||
let mut input2 = data.open()?;
|
||||
commands::merge_signatures(&mut input, &mut input2, output)?;
|
||||
} else if command.clearsign {
|
||||
let output = config.create_or_stdout_safe(output)?;
|
||||
let output = output.create_safe(config.force)?;
|
||||
commands::sign::clearsign(config, private_key_store, input, output, secrets,
|
||||
time, ¬ations)?;
|
||||
} else {
|
||||
@ -1263,10 +1162,8 @@ fn main() -> Result<()> {
|
||||
}
|
||||
},
|
||||
SqSubcommands::Verify(command) => {
|
||||
// TODO: Fix interface of open_or_stdin, create_or_stdout_safe, etc.
|
||||
let mut input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(command.io.output.as_deref())?;
|
||||
let mut input = command.input.open()?;
|
||||
let mut output = command.output.create_safe(config.force)?;
|
||||
let mut detached = if let Some(f) = command.detached {
|
||||
Some(File::open(f)?)
|
||||
} else {
|
||||
@ -1289,7 +1186,7 @@ fn main() -> Result<()> {
|
||||
|
||||
// TODO: Extract body to commands/armor.rs
|
||||
SqSubcommands::Armor(command) => {
|
||||
let input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let input = command.input.open()?;
|
||||
let mut want_kind: Option<armor::Kind> = command.kind.into();
|
||||
|
||||
// Peek at the data. If it looks like it is armored
|
||||
@ -1308,8 +1205,7 @@ fn main() -> Result<()> {
|
||||
&& (want_kind.is_none() || want_kind == have_kind)
|
||||
{
|
||||
// It is already armored and has the correct kind.
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(command.io.output.as_deref())?;
|
||||
let mut output = command.output.create_safe(c.force)?;
|
||||
io::copy(&mut input, &mut output)?;
|
||||
return Ok(());
|
||||
}
|
||||
@ -1324,8 +1220,7 @@ fn main() -> Result<()> {
|
||||
let want_kind = want_kind.expect("given or detected");
|
||||
|
||||
let mut output =
|
||||
config.create_or_stdout_pgp(command.io.output.as_deref(),
|
||||
false, want_kind)?;
|
||||
command.output.create_pgp_safe(config.force, false, want_kind)?;
|
||||
|
||||
if already_armored {
|
||||
// Dearmor and copy to change the type.
|
||||
@ -1339,9 +1234,8 @@ fn main() -> Result<()> {
|
||||
output.finalize()?;
|
||||
},
|
||||
SqSubcommands::Dearmor(command) => {
|
||||
let mut input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let mut output =
|
||||
config.create_or_stdout_safe(command.io.output.as_deref())?;
|
||||
let mut input = command.input.open()?;
|
||||
let mut output = command.output.create_safe(config.force)?;
|
||||
let mut filter = armor::Reader::from_reader(&mut input, None);
|
||||
io::copy(&mut filter, &mut output)?;
|
||||
},
|
||||
@ -1367,10 +1261,9 @@ fn main() -> Result<()> {
|
||||
|
||||
SqSubcommands::Packet(command) => match command.subcommand {
|
||||
packet::Subcommands::Dump(command) => {
|
||||
let mut input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let mut output = config.create_or_stdout_unsafe(
|
||||
command.io.output.as_deref(),
|
||||
)?;
|
||||
let mut input = command.input.open()?;
|
||||
let output_type = command.output;
|
||||
let mut output = output_type.create_unsafe(config.force)?;
|
||||
|
||||
let session_key = command.session_key;
|
||||
let width = if let Some((width, _)) = terminal_size() {
|
||||
@ -1384,9 +1277,9 @@ fn main() -> Result<()> {
|
||||
},
|
||||
|
||||
packet::Subcommands::Decrypt(command) => {
|
||||
let mut input = open_or_stdin(command.io.input.as_deref())?;
|
||||
let mut output = config.create_or_stdout_pgp(
|
||||
command.io.output.as_deref(),
|
||||
let mut input = command.input.open()?;
|
||||
let mut output = command.output.create_pgp_safe(
|
||||
config.force,
|
||||
command.binary,
|
||||
armor::Kind::Message,
|
||||
)?;
|
||||
@ -1404,15 +1297,16 @@ fn main() -> Result<()> {
|
||||
},
|
||||
|
||||
packet::Subcommands::Split(command) => {
|
||||
let mut input = open_or_stdin(command.input.as_deref())?;
|
||||
let mut input = command.input.open()?;
|
||||
let prefix =
|
||||
// The prefix is either specified explicitly...
|
||||
command.prefix.unwrap_or(
|
||||
// ... or we derive it from the input file...
|
||||
command.input.and_then(|i| {
|
||||
let p = PathBuf::from(i);
|
||||
command.input.and_then(|x| {
|
||||
// (but only use the filename)
|
||||
p.file_name().map(|f| String::from(f.to_string_lossy()))
|
||||
x.file_name().map(|f|
|
||||
String::from(f.to_string_lossy())
|
||||
)
|
||||
})
|
||||
// ... or we use a generic prefix...
|
||||
.unwrap_or_else(|| String::from("output"))
|
||||
|
@ -1,6 +1,9 @@
|
||||
use clap::Parser;
|
||||
|
||||
use crate::sq_cli::types::{ArmorKind, IoArgs};
|
||||
use super::types::ArmorKind;
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrStdin;
|
||||
use super::types::FileOrStdout;
|
||||
|
||||
// TODO?: Option<_> conflicts with default value
|
||||
// TODO: use indoc to transparently (de-)indent static strings
|
||||
@ -29,8 +32,20 @@ $ sq armor binary-message.pgp
|
||||
"
|
||||
)]
|
||||
pub struct Command {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
long = "label",
|
||||
value_name = "LABEL",
|
||||
|
@ -1,6 +1,8 @@
|
||||
use clap::{ValueEnum, Args, Parser, Subcommand};
|
||||
|
||||
use crate::sq_cli::types::IoArgs;
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrStdin;
|
||||
use super::types::FileOrStdout;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
@ -47,8 +49,20 @@ $ sq autocrypt decode autocrypt.eml
|
||||
"
|
||||
)]
|
||||
pub struct DecodeCommand {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(short = 'B', long, help = "Emits binary data")]
|
||||
pub binary: bool,
|
||||
}
|
||||
@ -81,8 +95,20 @@ $ sq autocrypt encode-sender --prefer-encrypt mutual juliet.pgp
|
||||
"
|
||||
)]
|
||||
pub struct EncodeSenderCommand {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
// TODO the help message looks like "primary userid" might be the default
|
||||
// email. clarify
|
||||
#[clap(
|
||||
|
@ -5,7 +5,9 @@ use clap::Parser;
|
||||
use crate::sq_cli::THIRD_PARTY_CERTIFICATION_VALIDITY_DURATION;
|
||||
use crate::sq_cli::THIRD_PARTY_CERTIFICATION_VALIDITY_IN_YEARS;
|
||||
|
||||
use super::types::ClapData;
|
||||
use super::types::Expiry;
|
||||
use super::types::FileOrStdout;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
@ -49,12 +51,13 @@ $ sq certify --time 20130721 neal.pgp ada.pgp Ada
|
||||
)]
|
||||
pub struct Command {
|
||||
#[clap(
|
||||
short,
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Writes to FILE or stdout if omitted"
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
short = 'B',
|
||||
long,
|
||||
|
@ -1,9 +1,10 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
use crate::sq_cli::types::NetworkPolicy;
|
||||
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrCertStore;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "dane",
|
||||
@ -63,10 +64,11 @@ pub struct GetCommand {
|
||||
)]
|
||||
pub binary: bool,
|
||||
#[clap(
|
||||
short,
|
||||
default_value_t = FileOrCertStore::default(),
|
||||
help = FileOrCertStore::HELP,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Writes to FILE instead of importing into the certificate store"
|
||||
short,
|
||||
value_name = FileOrCertStore::VALUE_NAME,
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: FileOrCertStore,
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use clap::Parser;
|
||||
|
||||
use crate::sq_cli::types::IoArgs;
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrStdin;
|
||||
use super::types::FileOrStdout;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
@ -28,6 +30,18 @@ $ sq dearmor ascii-message.pgp
|
||||
",
|
||||
)]
|
||||
pub struct Command {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
}
|
||||
|
@ -2,7 +2,10 @@ use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::sq_cli::types::{IoArgs, SessionKey};
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrStdin;
|
||||
use super::types::FileOrStdout;
|
||||
use super::types::SessionKey;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
@ -44,8 +47,20 @@ $ sq decrypt ciphertext.pgp
|
||||
)]
|
||||
// TODO use usize
|
||||
pub struct Command {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
short = 'n',
|
||||
long = "signatures",
|
||||
|
@ -6,7 +6,9 @@ use clap::{ValueEnum, Parser};
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::KeyHandle;
|
||||
|
||||
use crate::sq_cli::types::IoArgs;
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrStdin;
|
||||
use super::types::FileOrStdout;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
@ -39,8 +41,20 @@ $ sq encrypt --symmetric message.txt
|
||||
",
|
||||
)]
|
||||
pub struct Command {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
short = 'B',
|
||||
long,
|
||||
|
@ -1,10 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::KeyHandle;
|
||||
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrStdin;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "inspect",
|
||||
@ -42,10 +43,11 @@ $ sq inspect --time 20130721 cert.pgp
|
||||
)]
|
||||
pub struct Command {
|
||||
#[clap(
|
||||
value_name = "FILE",
|
||||
help = "Reads from FILE or stdin if omitted",
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: Option<PathBuf>,
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
long = "cert",
|
||||
value_name = "FINGERPRINT|KEYID",
|
||||
|
@ -4,7 +4,9 @@ use clap::{ValueEnum, ArgGroup, Args, Parser, Subcommand};
|
||||
|
||||
use sequoia_openpgp::cert::CipherSuite as SqCipherSuite;
|
||||
|
||||
use crate::sq_cli::types::IoArgs;
|
||||
use crate::sq_cli::types::ClapData;
|
||||
use crate::sq_cli::types::FileOrStdin;
|
||||
use crate::sq_cli::types::FileOrStdout;
|
||||
use crate::sq_cli::types::Expiry;
|
||||
use crate::sq_cli::types::Time;
|
||||
use crate::sq_cli::KEY_VALIDITY_DURATION;
|
||||
@ -249,8 +251,20 @@ $ sq key password --clear < juliet.encrypted_key.pgp > juliet.decrypted_key.pgp
|
||||
",
|
||||
)]
|
||||
pub struct PasswordCommand {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
long = "clear",
|
||||
help = "Emit a key with unencrypted secrets",
|
||||
@ -286,8 +300,20 @@ $ sq key extract-cert --output juliet.cert.pgp juliet.key.pgp
|
||||
",
|
||||
)]
|
||||
pub struct ExtractCertCommand {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
short = 'B',
|
||||
long,
|
||||
@ -344,8 +370,20 @@ $ sq key userid add --userid \"Juliet\" --creation-time 20210628 \\
|
||||
",
|
||||
)]
|
||||
pub struct UseridAddCommand {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
value_name = "USERID",
|
||||
short,
|
||||
@ -406,8 +444,20 @@ $ sq key userid strip --userid \"<juliet@example.org>\" \\
|
||||
",
|
||||
)]
|
||||
pub struct UseridStripCommand {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
value_name = "USERID",
|
||||
short,
|
||||
@ -469,17 +519,19 @@ pub struct AdoptCommand {
|
||||
)]
|
||||
pub allow_broken_crypto: bool,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
value_name = "TARGET-KEY",
|
||||
help = "Adds keys to TARGET-KEY",
|
||||
help = "Adds keys to TARGET-KEY or reads keys from stdin if omitted",
|
||||
)]
|
||||
pub certificate: Option<PathBuf>,
|
||||
pub certificate: FileOrStdin,
|
||||
#[clap(
|
||||
short,
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Writes to FILE or stdout if omitted"
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
short = 'B',
|
||||
long,
|
||||
@ -529,17 +581,19 @@ pub struct AttestCertificationsCommand {
|
||||
)]
|
||||
pub all: bool,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
value_name = "KEY",
|
||||
help = "Changes attestations on KEY",
|
||||
help = "Changes attestations on KEY or reads from stdin if omitted",
|
||||
)]
|
||||
pub key: Option<PathBuf>,
|
||||
pub key: FileOrStdin,
|
||||
#[clap(
|
||||
short,
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Writes to FILE or stdout if omitted"
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
short = 'B',
|
||||
long,
|
||||
@ -618,8 +672,20 @@ $ sq key subkey add --output alice-new.key.pgp --can-sign --cipher-suite rsa3k -
|
||||
#[clap(group(ArgGroup::new("sign-group").args(&["can_sign", "can_encrypt"])))]
|
||||
#[clap(group(ArgGroup::new("required-group").args(&["can_authenticate", "can_sign", "can_encrypt"]).required(true)))]
|
||||
pub struct SubkeyAddCommand {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
long = "private-key-store",
|
||||
value_name = "KEY_STORE",
|
||||
|
@ -2,6 +2,10 @@ use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrStdin;
|
||||
use super::types::FileOrStdout;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "keyring",
|
||||
@ -79,12 +83,13 @@ pub struct FilterCommand {
|
||||
#[clap(value_name = "FILE", help = "Reads from FILE or stdin if omitted")]
|
||||
pub input: Vec<PathBuf>,
|
||||
#[clap(
|
||||
short,
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Writes to FILE or stdout if omitted"
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
long = "userid",
|
||||
value_name = "USERID",
|
||||
@ -174,15 +179,16 @@ $ sq keyring join juliet.pgp romeo.pgp alice.pgp
|
||||
",
|
||||
)]
|
||||
pub struct JoinCommand {
|
||||
#[clap(value_name = "FILE", help = "Sets the input files to use")]
|
||||
#[clap(value_name = "FILE", help = "Reads from FILE or stdin if omitted")]
|
||||
pub input: Vec<PathBuf>,
|
||||
#[clap(
|
||||
short,
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Sets the output file to use"
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
short = 'B',
|
||||
long = "binary",
|
||||
@ -210,18 +216,16 @@ $ sq keyring merge certs.pgp romeo-updates.pgp
|
||||
",
|
||||
)]
|
||||
pub struct MergeCommand {
|
||||
#[clap(
|
||||
value_name = "FILE",
|
||||
help = "Reads from FILE",
|
||||
)]
|
||||
#[clap(value_name = "FILE", help = "Reads from FILE or stdin if omitted")]
|
||||
pub input: Vec<PathBuf>,
|
||||
#[clap(
|
||||
short,
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Writes to FILE or stdout if omitted"
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
short = 'B',
|
||||
long = "binary",
|
||||
@ -251,10 +255,11 @@ $ sq keyring filter --domain example.org certs.pgp | sq keyring list
|
||||
)]
|
||||
pub struct ListCommand {
|
||||
#[clap(
|
||||
value_name = "FILE",
|
||||
help = "Reads from FILE or stdin if omitted",
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: Option<PathBuf>,
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
long = "all-userids",
|
||||
help = "Lists all user ids",
|
||||
@ -288,10 +293,11 @@ $ sq keyring merge certs.pgp | sq keyring split
|
||||
)]
|
||||
pub struct SplitCommand {
|
||||
#[clap(
|
||||
value_name = "FILE",
|
||||
help = "Reads from FILE or stdin if omitted",
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: Option<PathBuf>,
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
short = 'p',
|
||||
long = "prefix",
|
||||
|
@ -1,9 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
use crate::sq_cli::types::NetworkPolicy;
|
||||
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrCertStore;
|
||||
use super::types::FileOrStdin;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "keyserver",
|
||||
@ -62,12 +64,13 @@ the usual way.
|
||||
)]
|
||||
pub struct GetCommand {
|
||||
#[clap(
|
||||
short,
|
||||
default_value_t = FileOrCertStore::default(),
|
||||
help = FileOrCertStore::HELP,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Writes to FILE instead of importing into the certificate store"
|
||||
short,
|
||||
value_name = FileOrCertStore::VALUE_NAME,
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: FileOrCertStore,
|
||||
#[clap(
|
||||
short = 'B',
|
||||
long,
|
||||
@ -88,6 +91,10 @@ pub struct GetCommand {
|
||||
about = "Sends a key",
|
||||
)]
|
||||
pub struct SendCommand {
|
||||
#[clap(value_name = "FILE", help = "Reads from FILE or stdin if omitted")]
|
||||
pub input: Option<PathBuf>,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
}
|
||||
|
@ -2,7 +2,11 @@ use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
use crate::sq_cli::types::{ArmorKind, IoArgs, SessionKey};
|
||||
use super::types::ArmorKind;
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrStdin;
|
||||
use super::types::FileOrStdout;
|
||||
use super::types::SessionKey;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
@ -64,8 +68,20 @@ $ sq packet dump --session-key AAAABBBBCCCC... ciphertext.pgp
|
||||
",
|
||||
)]
|
||||
pub struct DumpCommand {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
long = "session-key",
|
||||
value_name = "SESSION-KEY",
|
||||
@ -102,8 +118,20 @@ $ sq packet decrypt --recipient-file juliet.pgp ciphertext.pgp
|
||||
",
|
||||
)]
|
||||
pub struct DecryptCommand {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
short = 'B',
|
||||
long,
|
||||
@ -154,8 +182,12 @@ $ sq packet split juliet.pgp
|
||||
",
|
||||
)]
|
||||
pub struct SplitCommand {
|
||||
#[clap(value_name = "FILE", help = "Reads from FILE or stdin if omitted")]
|
||||
pub input: Option<PathBuf>,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
short = 'p',
|
||||
long = "prefix",
|
||||
@ -191,12 +223,13 @@ pub struct JoinCommand {
|
||||
#[clap(value_name = "FILE", help = "Reads from FILE or stdin if omitted")]
|
||||
pub input: Vec<PathBuf>,
|
||||
#[clap(
|
||||
short,
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Writes to FILE or stdout if omitted"
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
long = "label",
|
||||
value_name = "LABEL",
|
||||
|
@ -2,7 +2,9 @@ use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::sq_cli::types::IoArgs;
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrStdin;
|
||||
use super::types::FileOrStdout;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
@ -36,8 +38,20 @@ $ sq sign --time 20020304 --detached --signer-file juliet.pgp message.txt
|
||||
",
|
||||
)]
|
||||
pub struct Command {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
// TODO: Why capital B?
|
||||
#[clap(
|
||||
short = 'B',
|
||||
|
@ -1,7 +1,14 @@
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::io::stdin;
|
||||
use std::io::stdout;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
use std::time::SystemTime;
|
||||
|
||||
@ -9,28 +16,413 @@ use anyhow::anyhow;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
|
||||
use buffered_reader::BufferedReader;
|
||||
use buffered_reader::File;
|
||||
use buffered_reader::Generic;
|
||||
use chrono::{offset::Utc, DateTime};
|
||||
/// Common types for arguments of sq.
|
||||
use clap::{ValueEnum, Args};
|
||||
use clap::ValueEnum;
|
||||
|
||||
use openpgp::armor;
|
||||
use openpgp::fmt::hex;
|
||||
use openpgp::serialize::stream::Armorer;
|
||||
use openpgp::serialize::stream::Message;
|
||||
use openpgp::types::SymmetricAlgorithm;
|
||||
use sequoia_openpgp as openpgp;
|
||||
use terminal_size::terminal_size;
|
||||
|
||||
use crate::sq_cli::SECONDS_IN_DAY;
|
||||
use crate::sq_cli::SECONDS_IN_YEAR;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct IoArgs {
|
||||
#[clap(value_name = "FILE", help = "Reads from FILE or stdin if omitted")]
|
||||
pub input: Option<PathBuf>,
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Writes to FILE or stdout if omitted"
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
/// A type wrapping an AtomicBool to guard emitting a CLI warning only once
|
||||
struct CliWarningOnce(AtomicBool);
|
||||
/// A static `CliWarningOnce` indicating whether a warning about the unstable
|
||||
/// CLI has been emitted already
|
||||
static UNSTABLE_CLI_WARNING: CliWarningOnce =
|
||||
CliWarningOnce(AtomicBool::new(false));
|
||||
|
||||
impl CliWarningOnce {
|
||||
/// Emit a warning message only once
|
||||
pub fn warn(&self) {
|
||||
if !self.0.swap(true, Ordering::Relaxed) {
|
||||
// stdout is connected to a terminal, assume interactive use.
|
||||
if terminal_size().is_none()
|
||||
// For bash shells, we can use a very simple heuristic.
|
||||
// We simply look at whether the COLUMNS variable is defined in
|
||||
// our environment.
|
||||
&& std::env::var_os("COLUMNS").is_none() {
|
||||
eprintln!(
|
||||
"\nWARNING: sq does not have a stable CLI interface. \
|
||||
Use with caution in scripts.\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait to provide const &str for clap annotations for custom structs
|
||||
pub trait ClapData {
|
||||
/// The clap value name
|
||||
const VALUE_NAME: &'static str;
|
||||
/// The clap help text
|
||||
const HELP: &'static str;
|
||||
}
|
||||
|
||||
/// A type wrapping an optional PathBuf to use as stdin or file input
|
||||
///
|
||||
/// When creating `FileOrStdin` from `&str`, providing a `"-"` is interpreted
|
||||
/// as `None`, i.e. read from stdin. Providing other strings is interpreted as
|
||||
/// `Some(PathBuf)`, i.e. read from file.
|
||||
/// Use this if a CLI should allow input from a file and if unset from stdin.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// use clap::Args;
|
||||
///
|
||||
/// #[derive(Debug, Args)]
|
||||
/// #[clap(name = "example", about = "an example")]
|
||||
/// pub struct Example {
|
||||
/// #[clap(
|
||||
/// default_value_t = FileOrStdin::default(),
|
||||
/// help = FileOrStdin::HELP,
|
||||
/// value_name = FileOrStdin::VALUE_NAME,
|
||||
/// )]
|
||||
/// pub input: FileOrStdin,
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct FileOrStdin(Option<PathBuf>);
|
||||
|
||||
impl ClapData for FileOrStdin {
|
||||
const VALUE_NAME: &'static str = "FILE";
|
||||
const HELP: &'static str = "Reads from FILE or stdin if omitted";
|
||||
}
|
||||
|
||||
impl FileOrStdin {
|
||||
pub fn new(path: Option<PathBuf>) -> Self {
|
||||
FileOrStdin(path)
|
||||
}
|
||||
|
||||
/// Return a reference to the inner type
|
||||
pub fn inner(&self) -> Option<&PathBuf> {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
/// Returns `None` if `self.0` is `None`, otherwise calls f with the wrapped
|
||||
/// value and returns the result
|
||||
pub fn and_then<U, F>(self, f: F) -> Option<U>
|
||||
where
|
||||
F: FnOnce(PathBuf) -> Option<U>,
|
||||
{
|
||||
self.0.and_then(|x| f(x))
|
||||
}
|
||||
|
||||
/// Get a boxed BufferedReader for the FileOrStdin
|
||||
///
|
||||
/// Opens a file if there is Some(PathBuf), else opens stdin.
|
||||
pub fn open(&self) -> Result<Box<dyn BufferedReader<()>>> {
|
||||
if let Some(path) = self.inner() {
|
||||
Ok(Box::new(
|
||||
File::open(path)
|
||||
.with_context(|| format!("Failed to open {}", self))?))
|
||||
} else {
|
||||
Ok(Box::new(Generic::new(stdin(), None)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FileOrStdin {
|
||||
fn default() -> Self {
|
||||
FileOrStdin(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for FileOrStdin {
|
||||
fn from(value: PathBuf) -> Self {
|
||||
if value == PathBuf::from("-") {
|
||||
FileOrStdin::default()
|
||||
} else {
|
||||
FileOrStdin::new(Some(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<PathBuf>> for FileOrStdin {
|
||||
fn from(value: Option<PathBuf>) -> Self {
|
||||
if let Some(path) = value {
|
||||
FileOrStdin::from(path)
|
||||
} else {
|
||||
FileOrStdin::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Path> for FileOrStdin {
|
||||
fn from(value: &Path) -> Self {
|
||||
if Path::new("-") == value {
|
||||
FileOrStdin::default()
|
||||
} else {
|
||||
FileOrStdin::from(value.to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<&Path>> for FileOrStdin {
|
||||
fn from(value: Option<&Path>) -> Self {
|
||||
if let Some(path) = value {
|
||||
FileOrStdin::from(path)
|
||||
} else {
|
||||
FileOrStdin::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FileOrStdin {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
if "-" == s {
|
||||
Ok(FileOrStdin(None))
|
||||
} else {
|
||||
Ok(FileOrStdin(Some(PathBuf::from(s))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileOrStdin {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
match &self.0 {
|
||||
None => write!(f, "-"),
|
||||
Some(path) => write!(f, "{}", path.display()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type wrapping an optional PathBuf to use as stdout or file output
|
||||
///
|
||||
/// Use this if a CLI should allow output to a file and if unset output to
|
||||
/// a cert store.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// use clap::Args;
|
||||
///
|
||||
/// #[derive(Debug, Args)]
|
||||
/// #[clap(name = "example", about = "an example")]
|
||||
/// pub struct Example {
|
||||
/// #[clap(
|
||||
/// help = FileOrCertStore::HELP,
|
||||
/// long,
|
||||
/// short,
|
||||
/// value_name = FileOrCertStore::VALUE_NAME,
|
||||
/// )]
|
||||
/// pub output: Option<FileOrCertStore>,
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct FileOrCertStore(Option<PathBuf>);
|
||||
|
||||
impl ClapData for FileOrCertStore {
|
||||
const VALUE_NAME: &'static str = "FILE";
|
||||
const HELP: &'static str
|
||||
= "Writes to FILE instead of importing into the certificate store";
|
||||
}
|
||||
|
||||
impl FileOrCertStore {
|
||||
/// Consume self and return the inner PathBuf
|
||||
pub fn into_inner(self) -> Option<PathBuf> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Return a reference to the inner type
|
||||
pub fn path(&self) -> Option<&PathBuf> {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FileOrCertStore {
|
||||
fn default() -> Self {
|
||||
FileOrCertStore(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FileOrCertStore {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
Ok(FileOrCertStore(Some(PathBuf::from(s))))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileOrCertStore {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
match &self.0 {
|
||||
Some(path) => write!(f, "{}", path.display()),
|
||||
None => write!(f, "-"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type wrapping an optional PathBuf to use as stdout or file output
|
||||
///
|
||||
/// When creating `FileOrStdout` from `&str`, providing a `"-"` is interpreted
|
||||
/// as `None`, i.e. output to stdout. Providing other strings is interpreted as
|
||||
/// `Some(PathBuf)`, i.e. output to file.
|
||||
/// Use this if a CLI should allow output to a file and if unset output to
|
||||
/// stdout.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// use clap::Args;
|
||||
///
|
||||
/// #[derive(Debug, Args)]
|
||||
/// #[clap(name = "example", about = "an example")]
|
||||
/// pub struct Example {
|
||||
/// #[clap(
|
||||
/// default_value_t = FileOrStdout::default(),
|
||||
/// help = FileOrStdout::HELP,
|
||||
/// long,
|
||||
/// short,
|
||||
/// value_name = FileOrStdout::VALUE_NAME,
|
||||
/// )]
|
||||
/// pub output: FileOrStdout,
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct FileOrStdout(Option<PathBuf>);
|
||||
|
||||
impl ClapData for FileOrStdout {
|
||||
const VALUE_NAME: &'static str = "FILE";
|
||||
const HELP: &'static str = "Writes to FILE or stdout if omitted";
|
||||
}
|
||||
|
||||
impl FileOrStdout {
|
||||
pub fn new(path: Option<PathBuf>) -> Self {
|
||||
FileOrStdout(path)
|
||||
}
|
||||
|
||||
/// Return a reference to the optional PathBuf
|
||||
pub fn path(&self) -> Option<&PathBuf> {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
/// Opens the file (or stdout) for writing data that is safe for
|
||||
/// non-interactive use.
|
||||
///
|
||||
/// This is suitable for any kind of OpenPGP data, decrypted or
|
||||
/// authenticated payloads.
|
||||
pub fn create_safe(
|
||||
&self,
|
||||
force: bool,
|
||||
) -> Result<Box<dyn Write + Sync + Send>> {
|
||||
self.create(force)
|
||||
}
|
||||
|
||||
/// Opens the file (or stdout) for writing data that is NOT safe
|
||||
/// for non-interactive use.
|
||||
///
|
||||
/// If our heuristic detects non-interactive use, we will emit a
|
||||
/// warning once.
|
||||
pub fn create_unsafe(
|
||||
&self,
|
||||
force: bool,
|
||||
) -> Result<Box<dyn Write + Sync + Send>> {
|
||||
UNSTABLE_CLI_WARNING.warn();
|
||||
self.create(force)
|
||||
}
|
||||
|
||||
/// Opens the file (or stdout) for writing data that is safe for
|
||||
/// non-interactive use because it is an OpenPGP data stream.
|
||||
pub fn create_pgp_safe(
|
||||
&self,
|
||||
force: bool,
|
||||
binary: bool,
|
||||
kind: armor::Kind,
|
||||
) -> Result<Message> {
|
||||
let sink = self.create_safe(force)?;
|
||||
let mut message = Message::new(sink);
|
||||
if ! binary {
|
||||
message = Armorer::new(message).kind(kind).build()?;
|
||||
}
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Helper function, do not use directly. Instead, use create_or_stdout_safe
|
||||
/// or create_or_stdout_unsafe.
|
||||
fn create(&self, force: bool) -> Result<Box<dyn Write + Sync + Send>> {
|
||||
if let Some(path) = self.path() {
|
||||
if !path.exists() || force {
|
||||
Ok(Box::new(
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.open(path)
|
||||
.context("Failed to create output file")?,
|
||||
))
|
||||
} else {
|
||||
Err(anyhow::anyhow!(format!(
|
||||
"File {:?} exists, use \"sq --force ...\" to overwrite",
|
||||
self,
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Ok(Box::new(stdout()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FileOrStdout {
|
||||
fn default() -> Self {
|
||||
FileOrStdout(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileOrCertStore> for FileOrStdout {
|
||||
fn from(value: FileOrCertStore) -> Self {
|
||||
FileOrStdout::new(value.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for FileOrStdout {
|
||||
fn from(value: PathBuf) -> Self {
|
||||
if value == PathBuf::from("-") {
|
||||
FileOrStdout::default()
|
||||
} else {
|
||||
FileOrStdout::new(Some(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<PathBuf>> for FileOrStdout {
|
||||
fn from(value: Option<PathBuf>) -> Self {
|
||||
if let Some(path) = value {
|
||||
FileOrStdout::from(path)
|
||||
} else {
|
||||
FileOrStdout::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FileOrStdout {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
if "-" == s {
|
||||
Ok(FileOrStdout::default())
|
||||
} else {
|
||||
Ok(FileOrStdout(Some(PathBuf::from(s))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileOrStdout {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
match &self.0 {
|
||||
Some(path) => write!(f, "{}", path.display()),
|
||||
None => write!(f, "-"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone)]
|
||||
|
@ -5,7 +5,9 @@ use clap::Parser;
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::KeyHandle;
|
||||
|
||||
use crate::sq_cli::types::IoArgs;
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrStdin;
|
||||
use super::types::FileOrStdout;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
@ -59,8 +61,20 @@ $ sq verify --time 20130721 msg.pgp
|
||||
",
|
||||
)]
|
||||
pub struct Command {
|
||||
#[clap(flatten)]
|
||||
pub io: IoArgs,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
help = FileOrStdin::HELP,
|
||||
value_name = FileOrStdin::VALUE_NAME,
|
||||
)]
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdout::default(),
|
||||
help = FileOrStdout::HELP,
|
||||
long,
|
||||
short,
|
||||
value_name = FileOrStdout::VALUE_NAME,
|
||||
)]
|
||||
pub output: FileOrStdout,
|
||||
#[clap(
|
||||
long = "detached",
|
||||
value_name = "SIG",
|
||||
|
@ -1,9 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
use crate::sq_cli::types::NetworkPolicy;
|
||||
|
||||
use super::types::ClapData;
|
||||
use super::types::FileOrCertStore;
|
||||
use super::types::FileOrStdin;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "wkd",
|
||||
@ -89,12 +91,13 @@ pub struct GetCommand {
|
||||
)]
|
||||
pub binary: bool,
|
||||
#[clap(
|
||||
short,
|
||||
default_value_t = FileOrCertStore::default(),
|
||||
help = FileOrCertStore::HELP,
|
||||
long,
|
||||
value_name = "FILE",
|
||||
help = "Writes to FILE instead of importing into the certificate store"
|
||||
short,
|
||||
value_name = FileOrCertStore::VALUE_NAME,
|
||||
)]
|
||||
pub output: Option<PathBuf>,
|
||||
pub output: FileOrCertStore,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
@ -138,10 +141,11 @@ pub struct GenerateCommand {
|
||||
)]
|
||||
pub domain: String,
|
||||
#[clap(
|
||||
default_value_t = FileOrStdin::default(),
|
||||
value_name = "CERT-RING",
|
||||
help = "Adds certificates from CERT-RING to the WKD",
|
||||
help = "Adds certificates from CERT-RING (or stdin if omitted) to the WKD",
|
||||
)]
|
||||
pub input: Option<PathBuf>,
|
||||
pub input: FileOrStdin,
|
||||
#[clap(
|
||||
short = 'd',
|
||||
long = "direct-method",
|
||||
|
Loading…
Reference in New Issue
Block a user