diff options
author | Nora Widdecke <nora@sequoia-pgp.org> | 2022-08-09 21:19:01 +0200 |
---|---|---|
committer | Nora Widdecke <nora@sequoia-pgp.org> | 2022-08-11 12:07:14 +0200 |
commit | f6b267a25a0e4d59bdfdf0e066b4875e8484550d (patch) | |
tree | 4337deab812be7565cca9112ac1c170b3250af82 /sq | |
parent | c5dc911c4bce2edfc85df7fa68ccbfd7e114fd83 (diff) |
sq: Move common types into module.
Diffstat (limited to 'sq')
-rw-r--r-- | sq/src/commands/decrypt.rs | 10 | ||||
-rw-r--r-- | sq/src/commands/dump.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/armor.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/autocrypt.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/dearmor.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/decrypt.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/encrypt.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/key.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/keyserver.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/mod.rs | 237 | ||||
-rw-r--r-- | sq/src/sq_cli/packet.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/revoke.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/sign.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/types.rs | 219 | ||||
-rw-r--r-- | sq/src/sq_cli/verify.rs | 2 | ||||
-rw-r--r-- | sq/src/sq_cli/wkd.rs | 2 |
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 |