summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNora Widdecke <nora@sequoia-pgp.org>2022-08-09 21:19:01 +0200
committerNora Widdecke <nora@sequoia-pgp.org>2022-08-11 12:07:14 +0200
commitf6b267a25a0e4d59bdfdf0e066b4875e8484550d (patch)
tree4337deab812be7565cca9112ac1c170b3250af82
parentc5dc911c4bce2edfc85df7fa68ccbfd7e114fd83 (diff)
sq: Move common types into module.
-rw-r--r--sq/src/commands/decrypt.rs10
-rw-r--r--sq/src/commands/dump.rs2
-rw-r--r--sq/src/sq_cli/armor.rs2
-rw-r--r--sq/src/sq_cli/autocrypt.rs2
-rw-r--r--sq/src/sq_cli/dearmor.rs2
-rw-r--r--sq/src/sq_cli/decrypt.rs2
-rw-r--r--sq/src/sq_cli/encrypt.rs2
-rw-r--r--sq/src/sq_cli/key.rs2
-rw-r--r--sq/src/sq_cli/keyserver.rs2
-rw-r--r--sq/src/sq_cli/mod.rs237
-rw-r--r--sq/src/sq_cli/packet.rs2
-rw-r--r--sq/src/sq_cli/revoke.rs2
-rw-r--r--sq/src/sq_cli/sign.rs2
-rw-r--r--sq/src/sq_cli/types.rs219
-rw-r--r--sq/src/sq_cli/verify.rs2
-rw-r--r--sq/src/sq_cli/wkd.rs2
16 files changed, 242 insertions, 250 deletions
diff --git a/sq/src/commands/decrypt.rs b/sq/src/commands/decrypt.rs
index 25aecbe3..9a701917 100644
--- a/sq/src/commands/decrypt.rs
+++ b/sq/src/commands/decrypt.rs
@@ -25,7 +25,7 @@ use crate::{
dump::PacketDumper,
VHelper,
},
- sq_cli::SessionKey as CliSessionKey,
+ sq_cli,
};
trait PrivateKey {
@@ -94,7 +94,7 @@ struct Helper<'a> {
secret_keys: HashMap<KeyID, Box<dyn PrivateKey>>,
key_identities: HashMap<KeyID, Fingerprint>,
key_hints: HashMap<KeyID, String>,
- session_keys: Vec<CliSessionKey>,
+ session_keys: Vec<sq_cli::types::SessionKey>,
dump_session_key: bool,
dumper: Option<PacketDumper>,
}
@@ -102,7 +102,7 @@ struct Helper<'a> {
impl<'a> Helper<'a> {
fn new(config: &Config<'a>, private_key_store: Option<&str>,
signatures: usize, certs: Vec<Cert>, secrets: Vec<Cert>,
- session_keys: Vec<CliSessionKey>,
+ session_keys: Vec<sq_cli::types::SessionKey>,
dump_session_key: bool, dump: bool)
-> Self
{
@@ -364,7 +364,7 @@ pub fn decrypt(config: Config,
output: &mut dyn io::Write,
signatures: usize, certs: Vec<Cert>, secrets: Vec<Cert>,
dump_session_key: bool,
- sk: Vec<CliSessionKey>,
+ sk: Vec<sq_cli::types::SessionKey>,
dump: bool, hex: bool)
-> Result<()> {
let helper = Helper::new(&config, private_key_store, signatures, certs,
@@ -388,7 +388,7 @@ pub fn decrypt_unwrap(config: Config,
input: &mut (dyn io::Read + Sync + Send),
output: &mut dyn io::Write,
secrets: Vec<Cert>,
- session_keys: Vec<CliSessionKey>,
+ session_keys: Vec<sq_cli::types::SessionKey>,
dump_session_key: bool)
-> Result<()>
{
diff --git a/sq/src/commands/dump.rs b/sq/src/commands/dump.rs
index 606f5fc3..0b3d4157 100644
--- a/sq/src/commands/dump.rs
+++ b/sq/src/commands/dump.rs
@@ -12,7 +12,7 @@ use self::openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue};
use self::openpgp::crypto::S2K;
use self::openpgp::parse::{map::Map, Parse, PacketParserResult};
-use crate::sq_cli::SessionKey;
+use crate::sq_cli::types::SessionKey;
#[derive(Debug)]
pub enum Kind {
diff --git a/sq/src/sq_cli/armor.rs b/sq/src/sq_cli/armor.rs
index 034a68e9..4bf6c247 100644
--- a/sq/src/sq_cli/armor.rs
+++ b/sq/src/sq_cli/armor.rs
@@ -1,6 +1,6 @@
use clap::Parser;
-use super::{ArmorKind, IoArgs};
+use crate::sq_cli::types::{ArmorKind, IoArgs};
// TODO?: Option<_> conflicts with default value
// TODO: Use PathBuf as input type for more type safety? Investigate conversion
diff --git a/sq/src/sq_cli/autocrypt.rs b/sq/src/sq_cli/autocrypt.rs
index c6ec1817..bfc81c84 100644
--- a/sq/src/sq_cli/autocrypt.rs
+++ b/sq/src/sq_cli/autocrypt.rs
@@ -1,6 +1,6 @@
use clap::{ArgEnum, Args, Parser, Subcommand};
-use super::IoArgs;
+use crate::sq_cli::types::IoArgs;
#[derive(Parser, Debug)]
#[clap(
diff --git a/sq/src/sq_cli/dearmor.rs b/sq/src/sq_cli/dearmor.rs
index 8222eac7..33f623a3 100644
--- a/sq/src/sq_cli/dearmor.rs
+++ b/sq/src/sq_cli/dearmor.rs
@@ -1,6 +1,6 @@
use clap::Parser;
-use super::IoArgs;
+use crate::sq_cli::types::IoArgs;
#[derive(Parser, Debug)]
#[clap(
diff --git a/sq/src/sq_cli/decrypt.rs b/sq/src/sq_cli/decrypt.rs
index aa09f575..2e3c6a69 100644
--- a/sq/src/sq_cli/decrypt.rs
+++ b/sq/src/sq_cli/decrypt.rs
@@ -1,6 +1,6 @@
use clap::Parser;
-use super::{SessionKey, IoArgs};
+use crate::sq_cli::types::{IoArgs, SessionKey};
#[derive(Parser, Debug)]
#[clap(
diff --git a/sq/src/sq_cli/encrypt.rs b/sq/src/sq_cli/encrypt.rs
index 1e7a32ab..2f33e83e 100644
--- a/sq/src/sq_cli/encrypt.rs
+++ b/sq/src/sq_cli/encrypt.rs
@@ -1,6 +1,6 @@
use clap::{ArgEnum, Parser};
-use super::{IoArgs, Time};
+use crate::sq_cli::types::{IoArgs, Time};
#[derive(Parser, Debug)]
#[clap(
diff --git a/sq/src/sq_cli/key.rs b/sq/src/sq_cli/key.rs
index 93684304..526df6dd 100644
--- a/sq/src/sq_cli/key.rs
+++ b/sq/src/sq_cli/key.rs
@@ -1,6 +1,6 @@
use clap::{ArgEnum, ArgGroup, Args, Parser, Subcommand};
-use super::{IoArgs, Time};
+use crate::sq_cli::types::{IoArgs, Time};
#[derive(Parser, Debug)]
#[clap(
diff --git a/sq/src/sq_cli/keyserver.rs b/sq/src/sq_cli/keyserver.rs
index b5374b99..d4647ba2 100644
--- a/sq/src/sq_cli/keyserver.rs
+++ b/sq/src/sq_cli/keyserver.rs
@@ -1,6 +1,6 @@
use clap::{Args, Parser, Subcommand};
-use super::NetworkPolicy;
+use crate::sq_cli::types::NetworkPolicy;
#[derive(Parser, Debug)]
#[clap(
diff --git a/sq/src/sq_cli/mod.rs b/sq/src/sq_cli/mod.rs
index 65cce7bd..91748a1f 100644
--- a/sq/src/sq_cli/mod.rs
+++ b/sq/src/sq_cli/mod.rs
@@ -1,12 +1,5 @@
/// Command-line parser for sq.
-use clap::{Command, ArgEnum, Args, Subcommand};
-use clap::{CommandFactory, Parser};
-
-use sequoia_openpgp as openpgp;
-use openpgp::armor::Kind as OpenPGPArmorKind;
-use openpgp::crypto::SessionKey as OpenPGPSessionKey;
-use openpgp::types::SymmetricAlgorithm;
-use openpgp::fmt::hex;
+use clap::{Command, CommandFactory, Parser, Subcommand};
#[cfg(feature = "autocrypt")]
pub mod autocrypt;
@@ -27,6 +20,8 @@ mod sign;
mod verify;
pub mod wkd;
+pub mod types;
+
pub fn build() -> Command<'static> {
let sq_version = Box::leak(
format!(
@@ -62,13 +57,13 @@ to refer to OpenPGP keys that do contain secrets.
subcommand_required = true,
arg_required_else_help = true,
disable_colored_help = true,
- setting(clap::AppSettings::DeriveDisplayOrder),
+ setting(clap::AppSettings::DeriveDisplayOrder)
)]
pub struct SqCommand {
#[clap(
short = 'f',
long = "force",
- help = "Overwrites existing files",
+ help = "Overwrites existing files"
)]
pub force: bool,
#[clap(
@@ -145,225 +140,3 @@ pub enum SqSubcommands {
OutputVersions(output_versions::Command),
}
-
-use chrono::{offset::Utc, DateTime};
-#[derive(Debug)]
-pub struct Time {
- pub time: DateTime<Utc>,
-}
-
-impl std::str::FromStr for Time {
- type Err = anyhow::Error;
-
- fn from_str(s: &str) -> anyhow::Result<Time> {
- let time =
- Time::parse_iso8601(s, chrono::NaiveTime::from_hms(0, 0, 0))?;
- Ok(Time { time })
- }
-}
-
-impl Time {
- /// Parses the given string depicting a ISO 8601 timestamp.
- fn parse_iso8601(
- s: &str,
- pad_date_with: chrono::NaiveTime,
- ) -> anyhow::Result<DateTime<Utc>> {
- // If you modify this function this function, synchronize the
- // changes with the copy in sqv.rs!
- for f in &[
- "%Y-%m-%dT%H:%M:%S%#z",
- "%Y-%m-%dT%H:%M:%S",
- "%Y-%m-%dT%H:%M%#z",
- "%Y-%m-%dT%H:%M",
- "%Y-%m-%dT%H%#z",
- "%Y-%m-%dT%H",
- "%Y%m%dT%H%M%S%#z",
- "%Y%m%dT%H%M%S",
- "%Y%m%dT%H%M%#z",
- "%Y%m%dT%H%M",
- "%Y%m%dT%H%#z",
- "%Y%m%dT%H",
- ] {
- if f.ends_with("%#z") {
- if let Ok(d) = DateTime::parse_from_str(s, *f) {
- return Ok(d.into());
- }
- } else if let Ok(d) = chrono::NaiveDateTime::parse_from_str(s, *f) {
- return Ok(DateTime::from_utc(d, Utc));
- }
- }
- for f in &[
- "%Y-%m-%d",
- "%Y-%m",
- "%Y-%j",
- "%Y%m%d",
- "%Y%m",
- "%Y%j",
- "%Y",
- ] {
- if let Ok(d) = chrono::NaiveDate::parse_from_str(s, *f) {
- return Ok(DateTime::from_utc(d.and_time(pad_date_with), Utc));
- }
- }
- Err(anyhow::anyhow!("Malformed ISO8601 timestamp: {}", s))
- }
-}
-
-#[derive(Debug, Args)]
-pub struct IoArgs {
- #[clap(value_name = "FILE", help = "Reads from FILE or stdin if omitted")]
- pub input: Option<String>,
- #[clap(
- short,
- long,
- value_name = "FILE",
- help = "Writes to FILE or stdout if omitted"
- )]
- pub output: Option<String>,
-}
-
-#[derive(ArgEnum, Debug, Clone)]
-pub enum ArmorKind {
- Auto,
- Message,
- #[clap(name = "cert")]
- PublicKey,
- #[clap(name = "key")]
- SecretKey,
- #[clap(name = "sig")]
- Signature,
- File,
-}
-
-impl From<ArmorKind> for Option<OpenPGPArmorKind> {
-
- fn from(c: ArmorKind) -> Self {
- match c {
- ArmorKind::Auto => None,
- ArmorKind::Message => Some(OpenPGPArmorKind::Message),
- ArmorKind::PublicKey => Some(OpenPGPArmorKind::PublicKey),
- ArmorKind::SecretKey => Some(OpenPGPArmorKind::SecretKey),
- ArmorKind::Signature => Some(OpenPGPArmorKind::Signature),
- ArmorKind::File => Some(OpenPGPArmorKind::File),
- }
- }
-}
-
-#[derive(ArgEnum, Clone, Debug)]
-pub enum NetworkPolicy {
- Offline,
- Anonymized,
- Encrypted,
- Insecure,
-}
-
-impl From<NetworkPolicy> for sequoia_net::Policy {
- fn from(kp: NetworkPolicy) -> Self {
- match kp {
- NetworkPolicy::Offline => sequoia_net::Policy::Offline,
- NetworkPolicy::Anonymized => sequoia_net::Policy::Anonymized,
- NetworkPolicy::Encrypted => sequoia_net::Policy::Encrypted,
- NetworkPolicy::Insecure => sequoia_net::Policy::Insecure,
- }
- }
-}
-
-/// Holds a session key as parsed from the command line, with an optional
-/// algorithm specifier.
-///
-/// This struct does not implement [`Display`] to prevent accidental leaking
-/// of key material. If you are sure you want to print a session key, use
-/// [`display_sensitive`].
-///
-/// [`Display`]: std::fmt::Display
-/// [`display_sensitive`]: CliSessionKey::display_sensitive
-#[derive(Debug, Clone)]
-pub struct SessionKey {
- pub session_key: OpenPGPSessionKey,
- pub symmetric_algo: Option<SymmetricAlgorithm>,
-}
-
-impl std::str::FromStr for SessionKey {
- type Err = anyhow::Error;
-
- /// Parse a session key. The format is: an optional prefix specifying the
- /// symmetric algorithm as a number, followed by a colon, followed by the
- /// session key in hexadecimal representation.
- fn from_str(sk: &str) -> anyhow::Result<Self> {
- let result = if let Some((algo, sk)) = sk.split_once(':') {
- let algo = SymmetricAlgorithm::from(algo.parse::<u8>()?);
- let dsk = hex::decode_pretty(sk)?.into();
- SessionKey {
- session_key: dsk,
- symmetric_algo: Some(algo),
- }
- } else {
- let dsk = hex::decode_pretty(sk)?.into();
- SessionKey {
- session_key: dsk,
- symmetric_algo: None,
- }
- };
- Ok(result)
- }
-}
-
-impl SessionKey {
-
- /// Returns an object that implements Display for explicitly opting into
- /// printing a `SessionKey`.
- pub fn display_sensitive(&self) -> SessionKeyDisplay {
- SessionKeyDisplay { csk: self }
- }
-}
-
-/// Helper struct for intentionally printing session keys with format! and {}.
-///
-/// This struct implements the `Display` trait to print the session key. This
-/// construct requires the user to explicitly call
-/// [`CliSessionKey::display_sensitive`]. By requiring the user to opt-in, this
-/// will hopefully reduce that the chance that the session key is inadvertently
-/// leaked, e.g., in a log that may be publicly posted.
-pub struct SessionKeyDisplay<'a> {
- csk: &'a SessionKey,
-}
-
-/// Print the session key without prefix in hexadecimal representation.
-impl<'a> std::fmt::Display for SessionKeyDisplay<'a> {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- let sk = self.csk;
- write!(f, "{}", hex::encode(&sk.session_key))
- }
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn test_parse_iso8601() -> anyhow::Result<()> {
- let z = chrono::NaiveTime::from_hms(0, 0, 0);
- Time::parse_iso8601("2017-03-04T13:25:35Z", z)?;
- Time::parse_iso8601("2017-03-04T13:25:35+08:30", z)?;
- Time::parse_iso8601("2017-03-04T13:25:35", z)?;
- Time::parse_iso8601("2017-03-04T13:25Z", z)?;
- Time::parse_iso8601("2017-03-04T13:25", z)?;
- // CliTime::parse_iso8601("2017-03-04T13Z", z)?; // XXX: chrono doesn't like
- // CliTime::parse_iso8601("2017-03-04T13", z)?; // ditto
- Time::parse_iso8601("2017-03-04", z)?;
- // CliTime::parse_iso8601("2017-03", z)?; // ditto
- Time::parse_iso8601("2017-031", z)?;
- Time::parse_iso8601("20170304T132535Z", z)?;
- Time::parse_iso8601("20170304T132535+0830", z)?;
- Time::parse_iso8601("20170304T132535", z)?;
- Time::parse_iso8601("20170304T1325Z", z)?;
- Time::parse_iso8601("20170304T1325", z)?;
- // CliTime::parse_iso8601("20170304T13Z", z)?; // ditto
- // CliTime::parse_iso8601("20170304T13", z)?; // ditto
- Time::parse_iso8601("20170304", z)?;
- // CliTime::parse_iso8601("201703", z)?; // ditto
- Time::parse_iso8601("2017031", z)?;
- // CliTime::parse_iso8601("2017", z)?; // ditto
- Ok(())
- }
-}
diff --git a/sq/src/sq_cli/packet.rs b/sq/src/sq_cli/packet.rs
index 2fb20c81..28ccb97e 100644
--- a/sq/src/sq_cli/packet.rs
+++ b/sq/src/sq_cli/packet.rs
@@ -1,6 +1,6 @@
use clap::{Args, Parser, Subcommand};
-use super::{ArmorKind, IoArgs, SessionKey};
+use crate::sq_cli::types::{ArmorKind, IoArgs, SessionKey};
#[derive(Parser, Debug)]
#[clap(
diff --git a/sq/src/sq_cli/revoke.rs b/sq/src/sq_cli/revoke.rs
index 10e54ed6..824994a2 100644
--- a/sq/src/sq_cli/revoke.rs
+++ b/sq/src/sq_cli/revoke.rs
@@ -3,7 +3,7 @@ use clap::{ArgEnum, Args, Subcommand};
use sequoia_openpgp as openpgp;
-use super::Time;
+use crate::sq_cli::types::Time;
#[derive(Parser, Debug)]
#[clap(
diff --git a/sq/src/sq_cli/sign.rs b/sq/src/sq_cli/sign.rs
index 13c45c41..d18990be 100644
--- a/sq/src/sq_cli/sign.rs
+++ b/sq/src/sq_cli/sign.rs
@@ -1,6 +1,6 @@
use clap::Parser;
-use super::{IoArgs, Time};
+use crate::sq_cli::types::{IoArgs, Time};
#[derive(Parser, Debug)]
#[clap(
diff --git a/sq/src/sq_cli/types.rs b/sq/src/sq_cli/types.rs
new file mode 100644
index 00000000..e5ee306e
--- /dev/null
+++ b/sq/src/sq_cli/types.rs
@@ -0,0 +1,219 @@
+use chrono::{offset::Utc, DateTime};
+/// Common types for arguments of sq.
+use clap::{ArgEnum, Args};
+
+use openpgp::fmt::hex;
+use openpgp::types::SymmetricAlgorithm;
+use sequoia_openpgp as openpgp;
+
+#[derive(Debug, Args)]
+pub struct IoArgs {
+ #[clap(value_name = "FILE", help = "Reads from FILE or stdin if omitted")]
+ pub input: Option<String>,
+ #[clap(
+ short,
+ long,
+ value_name = "FILE",
+ help = "Writes to FILE or stdout if omitted"
+ )]
+ pub output: Option<String>,
+}
+
+#[derive(ArgEnum, Debug, Clone)]
+pub enum ArmorKind {
+ Auto,
+ Message,
+ #[clap(name = "cert")]
+ PublicKey,
+ #[clap(name = "key")]
+ SecretKey,
+ #[clap(name = "sig")]
+ Signature,
+ File,
+}
+
+impl From<ArmorKind> for Option<openpgp::armor::Kind> {
+ fn from(c: ArmorKind) -> Self {
+ match c {
+ ArmorKind::Auto => None,
+ ArmorKind::Message => Some(openpgp::armor::Kind::Message),
+ ArmorKind::PublicKey => Some(openpgp::armor::Kind::PublicKey),
+ ArmorKind::SecretKey => Some(openpgp::armor::Kind::SecretKey),
+ ArmorKind::Signature => Some(openpgp::armor::Kind::Signature),
+ ArmorKind::File => Some(openpgp::armor::Kind::File),
+ }
+ }
+}
+
+#[derive(ArgEnum, Clone, Debug)]
+pub enum NetworkPolicy {
+ Offline,
+ Anonymized,
+ Encrypted,
+ Insecure,
+}
+
+impl From<NetworkPolicy> for sequoia_net::Policy {
+ fn from(kp: NetworkPolicy) -> Self {
+ match kp {
+ NetworkPolicy::Offline => sequoia_net::Policy::Offline,
+ NetworkPolicy::Anonymized => sequoia_net::Policy::Anonymized,
+ NetworkPolicy::Encrypted => sequoia_net::Policy::Encrypted,
+ NetworkPolicy::Insecure => sequoia_net::Policy::Insecure,
+ }
+ }
+}
+
+/// Holds a session key as parsed from the command line, with an optional
+/// algorithm specifier.
+///
+/// This struct does not implement [`Display`] to prevent accidental leaking
+/// of key material. If you are sure you want to print a session key, use
+/// [`display_sensitive`].
+///
+/// [`Display`]: std::fmt::Display
+/// [`display_sensitive`]: SessionKey::display_sensitive
+#[derive(Debug, Clone)]
+pub struct SessionKey {
+ pub session_key: openpgp::crypto::SessionKey,
+ pub symmetric_algo: Option<SymmetricAlgorithm>,
+}
+
+impl std::str::FromStr for SessionKey {
+ type Err = anyhow::Error;
+
+ /// Parse a session key. The format is: an optional prefix specifying the
+ /// symmetric algorithm as a number, followed by a colon, followed by the
+ /// session key in hexadecimal representation.
+ fn from_str(sk: &str) -> anyhow::Result<Self> {
+ let result = if let Some((algo, sk)) = sk.split_once(':') {
+ let algo = SymmetricAlgorithm::from(algo.parse::<u8>()?);
+ let dsk = hex::decode_pretty(sk)?.into();
+ SessionKey {
+ session_key: dsk,
+ symmetric_algo: Some(algo),
+ }
+ } else {
+ let dsk = hex::decode_pretty(sk)?.into();
+ SessionKey {
+ session_key: dsk,
+ symmetric_algo: None,
+ }
+ };
+ Ok(result)
+ }
+}
+
+impl SessionKey {
+ /// Returns an object that implements Display for explicitly opting into
+ /// printing a `SessionKey`.
+ pub fn display_sensitive(&self) -> SessionKeyDisplay {
+ SessionKeyDisplay { csk: self }
+ }
+}
+
+/// Helper struct for intentionally printing session keys with format! and {}.
+///
+/// This struct implements the `Display` trait to print the session key. This
+/// construct requires the user to explicitly call
+/// [`SessionKey::display_sensitive`]. By requiring the user to opt-in, this
+/// will hopefully reduce that the chance that the session key is inadvertently
+/// leaked, e.g., in a log that may be publicly posted.
+pub struct SessionKeyDisplay<'a> {
+ csk: &'a SessionKey,
+}
+
+/// Print the session key without prefix in hexadecimal representation.
+impl<'a> std::fmt::Display for SessionKeyDisplay<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let sk = self.csk;
+ write!(f, "{}", hex::encode(&sk.session_key))
+ }
+}
+
+#[derive(Debug)]
+pub struct Time {
+ pub time: DateTime<Utc>,
+}
+
+impl std::str::FromStr for Time {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> anyhow::Result<Time> {
+ let time =
+ Time::parse_iso8601(s, chrono::NaiveTime::from_hms(0, 0, 0))?;
+ Ok(Time { time })
+ }
+}
+
+impl Time {
+ /// Parses the given string depicting a ISO 8601 timestamp.
+ fn parse_iso8601(
+ s: &str,
+ pad_date_with: chrono::NaiveTime,
+ ) -> anyhow::Result<DateTime<Utc>> {
+ // If you modify this function this function, synchronize the
+ // changes with the copy in sqv.rs!
+ for f in &[
+ "%Y-%m-%dT%H:%M:%S%#z",
+ "%Y-%m-%dT%H:%M:%S",
+ "%Y-%m-%dT%H:%M%#z",
+ "%Y-%m-%dT%H:%M",
+ "%Y-%m-%dT%H%#z",
+ "%Y-%m-%dT%H",
+ "%Y%m%dT%H%M%S%#z",
+ "%Y%m%dT%H%M%S",
+ "%Y%m%dT%H%M%#z",
+ "%Y%m%dT%H%M",
+ "%Y%m%dT%H%#z",
+ "%Y%m%dT%H",
+ ] {
+ if f.ends_with("%#z") {
+ if let Ok(d) = DateTime::parse_from_str(s, *f) {
+ return Ok(d.into());
+ }
+ } else if let Ok(d) = chrono::NaiveDateTime::parse_from_str(s, *f) {
+ return Ok(DateTime::from_utc(d, Utc));
+ }
+ }
+ for f in &["%Y-%m-%d", "%Y-%m", "%Y-%j", "%Y%m%d", "%Y%m", "%Y%j", "%Y"]
+ {
+ if let Ok(d) = chrono::NaiveDate::parse_from_str(s, *f) {
+ return Ok(DateTime::from_utc(d.and_time(pad_date_with), Utc));
+ }
+ }
+ Err(anyhow::anyhow!("Malformed ISO8601 timestamp: {}", s))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_parse_iso8601() -> anyhow::Result<()> {
+ let z = chrono::NaiveTime::from_hms(0, 0, 0);
+ Time::parse_iso8601("2017-03-04T13:25:35Z", z)?;
+ Time::parse_iso8601("2017-03-04T13:25:35+08:30", z)?;
+ Time::parse_iso8601("2017-03-04T13:25:35", z)?;
+ Time::parse_iso8601("2017-03-04T13:25Z", z)?;
+ Time::parse_iso8601("2017-03-04T13:25", z)?;
+ // CliTime::parse_iso8601("2017-03-04T13Z", z)?; // XXX: chrono doesn't like
+ // CliTime::parse_iso8601("2017-03-04T13", z)?; // ditto
+ Time::parse_iso8601("2017-03-04", z)?;
+ // CliTime::parse_iso8601("2017-03", z)?; // ditto
+ Time::parse_iso8601("2017-031", z)?;
+ Time::parse_iso8601("20170304T132535Z", z)?;
+ Time::parse_iso8601("20170304T132535+0830", z)?;
+ Time::parse_iso8601("20170304T132535", z)?;
+ Time::parse_iso8601("20170304T1325Z", z)?;
+ Time::parse_iso8601("20170304T1325", z)?;
+ // CliTime::parse_iso8601("20170304T13Z", z)?; // ditto
+ // CliTime::parse_iso8601("20170304T13", z)?; // ditto
+ Time::parse_iso8601("20170304", z)?;
+ // CliTime::parse_iso8601("201703", z)?; // ditto
+ Time::parse_iso8601("2017031", z)?;
+ // CliTime::parse_iso8601("2017", z)?; // ditto
+ Ok(())
+ }
+}
diff --git a/sq/src/sq_cli/verify.rs b/sq/src/sq_cli/verify.rs
index 2f56a30d..6d56ce93 100644
--- a/sq/src/sq_cli/verify.rs
+++ b/sq/src/sq_cli/verify.rs
@@ -1,6 +1,6 @@
use clap::Parser;
-use super::IoArgs;
+use crate::sq_cli::types::IoArgs;
#[derive(Parser, Debug)]
#[clap(
diff --git a/sq/src/sq_cli/wkd.rs b/sq/src/sq_cli/wkd.rs
index b90d4b0c..41a13ce5 100644
--- a/sq/src/sq_cli/wkd.rs
+++ b/