diff --git a/mcf/src/lib.rs b/mcf/src/lib.rs index 78495c2ce..c2ac6f204 100644 --- a/mcf/src/lib.rs +++ b/mcf/src/lib.rs @@ -25,10 +25,7 @@ pub use error::{Error, Result}; pub use fields::{Field, Fields}; #[cfg(feature = "alloc")] -use { - alloc::string::String, - core::{fmt, str}, -}; +pub use allocating::McfHash; /// Debug message used in panics when invariants aren't properly held. const INVARIANT_MSG: &str = "should be ensured valid by constructor"; @@ -72,110 +69,126 @@ impl<'a> McfHashRef<'a> { } } -/// Modular Crypt Format (MCF) serialized password hash. -/// -/// Password hashes in this format take the form `${id}$...`, where `{id}` is a short numeric or -/// alphanumeric algorithm identifier optionally containing a `-`, followed by `$` as a delimiter, -/// further followed by an algorithm-specific serialization of a password hash, typically -/// using a variant (often an algorithm-specific variant) of Base64. This algorithm-specific -/// serialization contains one or more fields `${first}[${second}]...`, where each field only uses -/// characters in the regexp range `[A-Za-z0-9./+=,\-]`. -/// -/// Example (SHA-crypt w\ SHA-512): -/// -/// ```text -/// $6$rounds=100000$exn6tVc2j/MZD8uG$BI1Xh8qQSK9J4m14uwy7abn.ctj/TIAzlaVCto0MQrOFIeTXsc1iwzH16XEWo/a7c7Y9eVJvufVzYAs4EsPOy0 -/// ``` -#[cfg(feature = "alloc")] -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct McfHash(String); - #[cfg(feature = "alloc")] -impl McfHash { - /// Parse the given input string, returning an [`McfHash`] if valid. - pub fn new(s: impl Into) -> Result { - let s = s.into(); - validate(&s)?; - Ok(Self(s)) - } +mod allocating { + use crate::{Base64, Error, Field, Fields, McfHashRef, Result, fields, validate, validate_id}; + use alloc::string::String; + use core::{fmt, str}; - /// Create an [`McfHash`] from an identifier. + /// Modular Crypt Format (MCF) serialized password hash. /// - /// # Returns + /// Password hashes in this format take the form `${id}$...`, where `{id}` is a short numeric or + /// alphanumeric algorithm identifier optionally containing a `-`, followed by `$` as a delimiter, + /// further followed by an algorithm-specific serialization of a password hash, typically + /// using a variant (often an algorithm-specific variant) of Base64. This algorithm-specific + /// serialization contains one or more fields `${first}[${second}]...`, where each field only uses + /// characters in the regexp range `[A-Za-z0-9./+=,\-]`. /// - /// Error if the identifier is invalid. + /// Example (SHA-crypt w\ SHA-512): /// - /// Allowed characters match the regex: `[a-z0-9\-]`, where the first and last characters do NOT - /// contain a `-`. - pub fn from_id(id: &str) -> Result { - validate_id(id)?; + /// ```text + /// $6$rounds=100000$exn6tVc2j/MZD8uG$BI1Xh8qQSK9J4m14uwy7abn.ctj/TIAzlaVCto0MQrOFIeTXsc1iwzH16XEWo/a7c7Y9eVJvufVzYAs4EsPOy0 + /// ``` + #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] + pub struct McfHash(String); + + impl McfHash { + /// Parse the given input string, returning an [`McfHash`] if valid. + pub fn new(s: impl Into) -> Result { + let s = s.into(); + validate(&s)?; + Ok(Self(s)) + } - let mut hash = String::with_capacity(1 + id.len()); - hash.push(fields::DELIMITER); - hash.push_str(id); - Ok(Self(hash)) - } + /// Create an [`McfHash`] from an identifier. + /// + /// # Returns + /// + /// Error if the identifier is invalid. + /// + /// Allowed characters match the regex: `[a-z0-9\-]`, where the first and last characters do NOT + /// contain a `-`. + pub fn from_id(id: &str) -> Result { + validate_id(id)?; + + let mut hash = String::with_capacity(1 + id.len()); + hash.push(fields::DELIMITER); + hash.push_str(id); + Ok(Self(hash)) + } - /// Get the contained string as a `str`. - pub fn as_str(&self) -> &str { - &self.0 - } + /// Get the contained string as a `str`. + pub fn as_str(&self) -> &str { + &self.0 + } - /// Get an [`McfHashRef`] which corresponds to this owned [`McfHash`]. - pub fn as_mcf_hash_ref(&self) -> McfHashRef<'_> { - McfHashRef(self.as_str()) - } + /// Get an [`McfHashRef`] which corresponds to this owned [`McfHash`]. + pub fn as_mcf_hash_ref(&self) -> McfHashRef<'_> { + McfHashRef(self.as_str()) + } - /// Get the algorithm identifier for this MCF hash. - pub fn id(&self) -> &str { - self.as_mcf_hash_ref().id() - } + /// Get the algorithm identifier for this MCF hash. + pub fn id(&self) -> &str { + self.as_mcf_hash_ref().id() + } - /// Get an iterator over the parts of the password hash as delimited by `$`, excluding the - /// initial identifier. - pub fn fields(&self) -> Fields<'_> { - self.as_mcf_hash_ref().fields() + /// Get an iterator over the parts of the password hash as delimited by `$`, excluding the + /// initial identifier. + pub fn fields(&self) -> Fields<'_> { + self.as_mcf_hash_ref().fields() + } + + /// Push an additional field onto the password hash string. + pub fn push_field(&mut self, field: Field<'_>) { + self.0.push(fields::DELIMITER); + self.0.push_str(field.as_str()); + } + + /// Push an additional field onto the password hash string, encoding it first as Base64. + pub fn push_field_base64(&mut self, field: &[u8], base64_encoding: Base64) { + self.0.push(fields::DELIMITER); + self.0.push_str(&base64_encoding.encode_string(field)); + } } - /// Push an additional field onto the password hash string. - pub fn push_field(&mut self, field: Field<'_>) { - self.0.push(fields::DELIMITER); - self.0.push_str(field.as_str()); + impl<'a> AsRef for McfHashRef<'a> { + fn as_ref(&self) -> &str { + self.as_str() + } } - /// Push an additional field onto the password hash string, encoding it first as Base64. - pub fn push_field_base64(&mut self, field: &[u8], base64_encoding: Base64) { - self.0.push(fields::DELIMITER); - self.0.push_str(&base64_encoding.encode_string(field)); + impl AsRef for McfHash { + fn as_ref(&self) -> &str { + self.as_str() + } } -} -impl<'a> AsRef for McfHashRef<'a> { - fn as_ref(&self) -> &str { - self.as_str() + impl From for String { + fn from(hash: McfHash) -> Self { + hash.0 + } } -} -#[cfg(feature = "alloc")] -impl AsRef for McfHash { - fn as_ref(&self) -> &str { - self.as_str() + impl TryFrom for McfHash { + type Error = Error; + + fn try_from(s: String) -> Result { + Self::new(s) + } } -} -#[cfg(feature = "alloc")] -impl fmt::Display for McfHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) + impl fmt::Display for McfHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } } -} -#[cfg(feature = "alloc")] -impl str::FromStr for McfHash { - type Err = Error; + impl str::FromStr for McfHash { + type Err = Error; - fn from_str(s: &str) -> Result { - Self::new(s) + fn from_str(s: &str) -> Result { + Self::new(s) + } } }