Coverage Report

Created: 2025-02-21 07:11

/rust/registry/src/index.crates.io-6f17d22bba15001f/password-hash-0.5.0/src/value.rs
Line
Count
Source (jump to first uncovered line)
1
//! Algorithm parameter value as defined by the [PHC string format].
2
//!
3
//! Implements the following parts of the specification:
4
//!
5
//! > The value for each parameter consists in characters in: `[a-zA-Z0-9/+.-]`
6
//! > (lowercase letters, uppercase letters, digits, /, +, . and -). No other
7
//! > character is allowed. Interpretation of the value depends on the
8
//! > parameter and the function. The function specification MUST unambiguously
9
//! > define the set of valid parameter values. The function specification MUST
10
//! > define a maximum length (in characters) for each parameter. For numerical
11
//! > parameters, functions SHOULD use plain decimal encoding (other encodings
12
//! > are possible as long as they are clearly defined).
13
//!
14
//! [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
15
16
use crate::errors::InvalidValue;
17
use crate::{Encoding, Error, Result};
18
use core::{fmt, str};
19
20
/// Type used to represent decimal (i.e. integer) values.
21
pub type Decimal = u32;
22
23
/// Algorithm parameter value string.
24
///
25
/// Parameter values are defined in the [PHC string format specification][1].
26
///
27
/// # Constraints
28
/// - ASCII-encoded string consisting of the characters `[a-zA-Z0-9/+.-]`
29
///   (lowercase letters, digits, and the minus sign)
30
/// - Minimum length: 0 (i.e. empty values are allowed)
31
/// - Maximum length: 64 ASCII characters (i.e. 64-bytes)
32
///
33
/// # Additional Notes
34
/// The PHC spec allows for algorithm-defined maximum lengths for parameter
35
/// values, however this library defines a [`Value::MAX_LENGTH`] of 64 ASCII
36
/// characters.
37
///
38
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
39
/// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
40
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
41
pub struct Value<'a>(&'a str);
42
43
impl<'a> Value<'a> {
44
    /// Maximum length of an [`Value`] - 64 ASCII characters (i.e. 64-bytes).
45
    ///
46
    /// This value is selected to match the maximum length of a [`Salt`][`crate::Salt`]
47
    /// as this library internally uses this type to represent salts.
48
    pub const MAX_LENGTH: usize = 64;
49
50
    /// Parse a [`Value`] from the provided `str`, validating it according to
51
    /// the PHC string format's rules.
52
8.40k
    pub fn new(input: &'a str) -> Result<Self> {
53
8.40k
        if input.as_bytes().len() > Self::MAX_LENGTH {
54
0
            return Err(Error::ParamValueInvalid(InvalidValue::TooLong));
55
8.40k
        }
56
8.40k
57
8.40k
        // Check that the characters are permitted in a PHC parameter value.
58
8.40k
        assert_valid_value(input)?;
59
8.40k
        Ok(Self(input))
60
8.40k
    }
61
62
    /// Attempt to decode a B64-encoded [`Value`], writing the decoded
63
    /// result into the provided buffer, and returning a slice of the buffer
64
    /// containing the decoded result on success.
65
    ///
66
    /// Examples of "B64"-encoded parameters in practice are the `keyid` and
67
    /// `data` parameters used by the [Argon2 Encoding][1] as described in the
68
    /// PHC string format specification.
69
    ///
70
    /// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
71
2.10k
    pub fn b64_decode<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> {
72
2.10k
        Ok(Encoding::B64.decode(self.as_str(), buf)?)
73
2.10k
    }
74
75
    /// Borrow this value as a `str`.
76
4.20k
    pub fn as_str(&self) -> &'a str {
77
4.20k
        self.0
78
4.20k
    }
79
80
    /// Borrow this value as bytes.
81
0
    pub fn as_bytes(&self) -> &'a [u8] {
82
0
        self.as_str().as_bytes()
83
0
    }
84
85
    /// Get the length of this value in ASCII characters.
86
0
    pub fn len(&self) -> usize {
87
0
        self.as_str().len()
88
0
    }
89
90
    /// Is this value empty?
91
0
    pub fn is_empty(&self) -> bool {
92
0
        self.as_str().is_empty()
93
0
    }
94
95
    /// Attempt to parse this [`Value`] as a PHC-encoded decimal (i.e. integer).
96
    ///
97
    /// Decimal values are integers which follow the rules given in the
98
    /// ["Decimal Encoding" section of the PHC string format specification][1].
99
    ///
100
    /// The decimal encoding rules are as follows:
101
    /// > For an integer value x, its decimal encoding consist in the following:
102
    /// >
103
    /// > - If x < 0, then its decimal encoding is the minus sign - followed by the decimal
104
    /// >   encoding of -x.
105
    /// > - If x = 0, then its decimal encoding is the single character 0.
106
    /// > - If x > 0, then its decimal encoding is the smallest sequence of ASCII digits that
107
    /// >   matches its value (i.e. there is no leading zero).
108
    /// >
109
    /// > Thus, a value is a valid decimal for an integer x if and only if all of the following hold true:
110
    /// >
111
    /// > - The first character is either a - sign, or an ASCII digit.
112
    /// > - All characters other than the first are ASCII digits.
113
    /// > - If the first character is - sign, then there is at least another character, and the
114
    /// >   second character is not a 0.
115
    /// > - If the string consists in more than one character, then the first one cannot be a 0.
116
    ///
117
    /// Note: this implementation does not support negative decimals despite
118
    /// them being allowed per the spec above. If you need to parse a negative
119
    /// number, please parse it from the string representation directly e.g.
120
    /// `value.as_str().parse::<i32>()`
121
    ///
122
    /// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#decimal-encoding
123
0
    pub fn decimal(&self) -> Result<Decimal> {
124
0
        let value = self.as_str();
125
0
126
0
        // Empty strings aren't decimals
127
0
        if value.is_empty() {
128
0
            return Err(Error::ParamValueInvalid(InvalidValue::Malformed));
129
0
        }
130
131
        // Ensure all characters are digits
132
0
        for c in value.chars() {
133
0
            if !c.is_ascii_digit() {
134
0
                return Err(Error::ParamValueInvalid(InvalidValue::InvalidChar(c)));
135
0
            }
136
        }
137
138
        // Disallow leading zeroes
139
0
        if value.starts_with('0') && value.len() > 1 {
140
0
            return Err(Error::ParamValueInvalid(InvalidValue::InvalidFormat));
141
0
        }
142
0
143
0
        value.parse().map_err(|_| {
144
0
            // In theory a value overflow should be the only potential error here.
145
0
            // When `ParseIntError::kind` is stable it might be good to double check:
146
0
            // <https://github.com/rust-lang/rust/issues/22639>
147
0
            Error::ParamValueInvalid(InvalidValue::InvalidFormat)
148
0
        })
149
0
    }
150
151
    /// Does this value parse successfully as a decimal?
152
0
    pub fn is_decimal(&self) -> bool {
153
0
        self.decimal().is_ok()
154
0
    }
155
}
156
157
impl<'a> AsRef<str> for Value<'a> {
158
0
    fn as_ref(&self) -> &str {
159
0
        self.as_str()
160
0
    }
161
}
162
163
impl<'a> TryFrom<&'a str> for Value<'a> {
164
    type Error = Error;
165
166
8.40k
    fn try_from(input: &'a str) -> Result<Self> {
167
8.40k
        Self::new(input)
168
8.40k
    }
169
}
170
171
impl<'a> TryFrom<Value<'a>> for Decimal {
172
    type Error = Error;
173
174
0
    fn try_from(value: Value<'a>) -> Result<Decimal> {
175
0
        Decimal::try_from(&value)
176
0
    }
177
}
178
179
impl<'a> TryFrom<&Value<'a>> for Decimal {
180
    type Error = Error;
181
182
0
    fn try_from(value: &Value<'a>) -> Result<Decimal> {
183
0
        value.decimal()
184
0
    }
185
}
186
187
impl<'a> fmt::Display for Value<'a> {
188
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189
0
        f.write_str(self.as_str())
190
0
    }
191
}
192
193
/// Are all of the given bytes allowed in a [`Value`]?
194
8.40k
fn assert_valid_value(input: &str) -> Result<()> {
195
69.3k
    for c in input.chars() {
196
69.3k
        if !is_char_valid(c) {
197
0
            return Err(Error::ParamValueInvalid(InvalidValue::InvalidChar(c)));
198
69.3k
        }
199
    }
200
201
8.40k
    Ok(())
202
8.40k
}
203
204
/// Ensure the given ASCII character (i.e. byte) is allowed in a [`Value`].
205
69.3k
fn is_char_valid(c: char) -> bool {
206
69.3k
    matches!(c, 'A' ..= 'Z' | 'a'..='z' | '0'..='9' | '/' | '+' | '.' | '-')
207
68.6k
}
208
209
#[cfg(test)]
210
mod tests {
211
    use super::{Error, InvalidValue, Value};
212
213
    // Invalid value examples
214
    const INVALID_CHAR: &str = "x;y";
215
    const INVALID_TOO_LONG: &str =
216
        "01234567891123456789212345678931234567894123456785234567896234567";
217
    const INVALID_CHAR_AND_TOO_LONG: &str =
218
        "0!234567891123456789212345678931234567894123456785234567896234567";
219
220
    //
221
    // Decimal parsing tests
222
    //
223
224
    #[test]
225
    fn decimal_value() {
226
        let valid_decimals = &[("0", 0u32), ("1", 1u32), ("4294967295", u32::MAX)];
227
228
        for &(s, i) in valid_decimals {
229
            let value = Value::new(s).unwrap();
230
            assert!(value.is_decimal());
231
            assert_eq!(value.decimal().unwrap(), i)
232
        }
233
    }
234
235
    #[test]
236
    fn reject_decimal_with_leading_zero() {
237
        let value = Value::new("01").unwrap();
238
        let err = u32::try_from(value).err().unwrap();
239
        assert!(matches!(
240
            err,
241
            Error::ParamValueInvalid(InvalidValue::InvalidFormat)
242
        ));
243
    }
244
245
    #[test]
246
    fn reject_overlong_decimal() {
247
        let value = Value::new("4294967296").unwrap();
248
        let err = u32::try_from(value).err().unwrap();
249
        assert_eq!(err, Error::ParamValueInvalid(InvalidValue::InvalidFormat));
250
    }
251
252
    #[test]
253
    fn reject_negative() {
254
        let value = Value::new("-1").unwrap();
255
        let err = u32::try_from(value).err().unwrap();
256
        assert!(matches!(
257
            err,
258
            Error::ParamValueInvalid(InvalidValue::InvalidChar(_))
259
        ));
260
    }
261
262
    //
263
    // String parsing tests
264
    //
265
266
    #[test]
267
    fn string_value() {
268
        let valid_examples = [
269
            "",
270
            "X",
271
            "x",
272
            "xXx",
273
            "a+b.c-d",
274
            "1/2",
275
            "01234567891123456789212345678931",
276
        ];
277
278
        for &example in &valid_examples {
279
            let value = Value::new(example).unwrap();
280
            assert_eq!(value.as_str(), example);
281
        }
282
    }
283
284
    #[test]
285
    fn reject_invalid_char() {
286
        let err = Value::new(INVALID_CHAR).err().unwrap();
287
        assert!(matches!(
288
            err,
289
            Error::ParamValueInvalid(InvalidValue::InvalidChar(_))
290
        ));
291
    }
292
293
    #[test]
294
    fn reject_too_long() {
295
        let err = Value::new(INVALID_TOO_LONG).err().unwrap();
296
        assert_eq!(err, Error::ParamValueInvalid(InvalidValue::TooLong));
297
    }
298
299
    #[test]
300
    fn reject_invalid_char_and_too_long() {
301
        let err = Value::new(INVALID_CHAR_AND_TOO_LONG).err().unwrap();
302
        assert_eq!(err, Error::ParamValueInvalid(InvalidValue::TooLong));
303
    }
304
}