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