/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 | | } |