digest_auth/
enums.rs

1#![allow(clippy::upper_case_acronyms)]
2
3use crate::{Error, Error::*, Result};
4use std::fmt;
5use std::fmt::{Display, Formatter};
6use std::str::FromStr;
7
8use digest::{Digest, DynDigest};
9use md5::Md5;
10use sha2::{Sha256, Sha512_256};
11use std::borrow::Cow;
12
13/// Algorithm type
14#[derive(Debug, PartialEq, Clone, Copy)]
15#[allow(non_camel_case_types)]
16pub enum AlgorithmType {
17    MD5,
18    SHA2_256,
19    SHA2_512_256,
20}
21
22/// Algorithm and the -sess flag pair
23#[derive(Debug, PartialEq, Clone, Copy)]
24pub struct Algorithm {
25    pub algo: AlgorithmType,
26    pub sess: bool,
27}
28
29impl Algorithm {
30    /// Compose from algorithm type and the -sess flag
31    pub fn new(algo: AlgorithmType, sess: bool) -> Algorithm {
32        Algorithm { algo, sess }
33    }
34
35    /// Calculate a hash of bytes using the selected algorithm
36    pub fn hash(self, bytes: &[u8]) -> String {
37        let mut hash: Box<dyn DynDigest> = match self.algo {
38            AlgorithmType::MD5 => Box::new(Md5::new()),
39            AlgorithmType::SHA2_256 => Box::new(Sha256::new()),
40            AlgorithmType::SHA2_512_256 => Box::new(Sha512_256::new()),
41        };
42
43        hash.update(bytes);
44        hex::encode(hash.finalize())
45    }
46
47    /// Calculate a hash of string's bytes using the selected algorithm
48    pub fn hash_str(self, bytes: &str) -> String {
49        self.hash(bytes.as_bytes())
50    }
51}
52
53impl FromStr for Algorithm {
54    type Err = Error;
55
56    /// Parse from the format used in WWW-Authorization
57    fn from_str(s: &str) -> Result<Self> {
58        match s {
59            "MD5" => Ok(Algorithm::new(AlgorithmType::MD5, false)),
60            "MD5-sess" => Ok(Algorithm::new(AlgorithmType::MD5, true)),
61            "SHA-256" => Ok(Algorithm::new(AlgorithmType::SHA2_256, false)),
62            "SHA-256-sess" => Ok(Algorithm::new(AlgorithmType::SHA2_256, true)),
63            "SHA-512-256" => Ok(Algorithm::new(AlgorithmType::SHA2_512_256, false)),
64            "SHA-512-256-sess" => Ok(Algorithm::new(AlgorithmType::SHA2_512_256, true)),
65            _ => Err(UnknownAlgorithm(s.into())),
66        }
67    }
68}
69
70impl Default for Algorithm {
71    /// Get a MD5 instance
72    fn default() -> Self {
73        Algorithm::new(AlgorithmType::MD5, false)
74    }
75}
76
77impl Display for Algorithm {
78    /// Format to the form used in HTTP headers
79    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
80        f.write_str(match self.algo {
81            AlgorithmType::MD5 => "MD5",
82            AlgorithmType::SHA2_256 => "SHA-256",
83            AlgorithmType::SHA2_512_256 => "SHA-512-256",
84        })?;
85
86        if self.sess {
87            f.write_str("-sess")?;
88        }
89
90        Ok(())
91    }
92}
93
94/// QOP field values
95#[derive(Debug, PartialEq, Clone, Copy)]
96#[allow(non_camel_case_types)]
97pub enum Qop {
98    AUTH,
99    AUTH_INT,
100}
101
102impl FromStr for Qop {
103    type Err = Error;
104
105    /// Parse from "auth" or "auth-int" as used in HTTP headers
106    fn from_str(s: &str) -> Result<Self> {
107        match s {
108            "auth" => Ok(Qop::AUTH),
109            "auth-int" => Ok(Qop::AUTH_INT),
110            _ => Err(BadQop(s.into())),
111        }
112    }
113}
114
115impl Display for Qop {
116    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117        f.write_str(match self {
118            Qop::AUTH => "auth",
119            Qop::AUTH_INT => "auth-int",
120        })
121    }
122}
123
124#[derive(Debug)]
125#[allow(non_camel_case_types)]
126pub enum QopAlgo<'a> {
127    NONE,
128    AUTH,
129    AUTH_INT(&'a [u8]),
130}
131
132// casting back...
133impl<'a> From<QopAlgo<'a>> for Option<Qop> {
134    fn from(algo: QopAlgo<'a>) -> Self {
135        match algo {
136            QopAlgo::NONE => None,
137            QopAlgo::AUTH => Some(Qop::AUTH),
138            QopAlgo::AUTH_INT(_) => Some(Qop::AUTH_INT),
139        }
140    }
141}
142
143/// Charset field value as specified by the server
144#[derive(Debug, PartialEq, Clone)]
145pub enum Charset {
146    ASCII,
147    UTF8,
148}
149
150impl FromStr for Charset {
151    type Err = Error;
152
153    /// Parse from string (only UTF-8 supported, as prescribed by the specification)
154    fn from_str(s: &str) -> Result<Self> {
155        match s {
156            "UTF-8" => Ok(Charset::UTF8),
157            _ => Err(BadCharset(s.into())),
158        }
159    }
160}
161
162impl Display for Charset {
163    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
164        f.write_str(match self {
165            Charset::ASCII => "ASCII",
166            Charset::UTF8 => "UTF-8",
167        })
168    }
169}
170
171/// HTTP method (used when generating the response hash for some Qop options)
172#[derive(Debug, PartialEq, Clone)]
173pub struct HttpMethod<'a>(pub Cow<'a, str>);
174
175// Well-known methods are provided as convenient associated constants
176impl<'a> HttpMethod<'a> {
177    pub const GET : Self = HttpMethod(Cow::Borrowed("GET"));
178    pub const POST : Self = HttpMethod(Cow::Borrowed("POST"));
179    pub const PUT : Self = HttpMethod(Cow::Borrowed("PUT"));
180    pub const DELETE : Self = HttpMethod(Cow::Borrowed("DELETE"));
181    pub const HEAD : Self = HttpMethod(Cow::Borrowed("HEAD"));
182    pub const OPTIONS : Self = HttpMethod(Cow::Borrowed("OPTIONS"));
183    pub const CONNECT : Self = HttpMethod(Cow::Borrowed("CONNECT"));
184    pub const PATCH : Self = HttpMethod(Cow::Borrowed("PATCH"));
185    pub const TRACE : Self = HttpMethod(Cow::Borrowed("TRACE"));
186}
187
188impl<'a> Default for HttpMethod<'a> {
189    fn default() -> Self {
190        HttpMethod::GET
191    }
192}
193
194impl<'a> Display for HttpMethod<'a> {
195    /// Convert to uppercase string
196    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
197        f.write_str(&self.0)
198    }
199}
200
201impl<'a> From<&'a str> for HttpMethod<'a> {
202    fn from(s: &'a str) -> Self {
203        Self(s.into())
204    }
205}
206
207impl<'a> From<&'a [u8]> for HttpMethod<'a> {
208    fn from(s: &'a [u8]) -> Self {
209        Self(String::from_utf8_lossy(s).into())
210    }
211}
212
213impl<'a> From<String> for HttpMethod<'a> {
214    fn from(s: String) -> Self {
215        Self(s.into())
216    }
217}
218
219impl<'a> From<Cow<'a, str>> for HttpMethod<'a> {
220    fn from(s: Cow<'a, str>) -> Self {
221        Self(s)
222    }
223}
224
225#[cfg(feature = "http")]
226impl From<http::Method> for HttpMethod<'static> {
227    fn from(method: http::Method) -> Self {
228        match method.as_str() {
229            // Avoid cloning when possible
230            "GET" => Self::GET,
231            "POST" => Self::POST,
232            "PUT" => Self::PUT,
233            "DELETE" => Self::DELETE,
234            "HEAD" => Self::HEAD,
235            "OPTIONS" => Self::OPTIONS,
236            "CONNECT" => Self::CONNECT,
237            "PATCH" => Self::PATCH,
238            "TRACE" => Self::TRACE,
239            // Clone custom strings. This is inefficient, but the inner string is private
240            other => Self(other.to_owned().into())
241        }
242    }
243}
244
245#[cfg(feature = "http")]
246impl<'a> From<&'a http::Method> for HttpMethod<'a> {
247    fn from(method: &'a http::Method) -> HttpMethod<'a> {
248        Self(method.as_str().into())
249    }
250}
251
252#[cfg(test)]
253mod test {
254    use crate::error::Error::{BadCharset, BadQop, UnknownAlgorithm};
255    use crate::{Algorithm, AlgorithmType, Charset, HttpMethod, Qop, QopAlgo};
256    use std::borrow::Cow;
257    use std::str::FromStr;
258
259    #[test]
260    fn test_algorithm_type() {
261        // String parsing
262        assert_eq!(
263            Ok(Algorithm::new(AlgorithmType::MD5, false)),
264            Algorithm::from_str("MD5")
265        );
266        assert_eq!(
267            Ok(Algorithm::new(AlgorithmType::MD5, true)),
268            Algorithm::from_str("MD5-sess")
269        );
270        assert_eq!(
271            Ok(Algorithm::new(AlgorithmType::SHA2_256, false)),
272            Algorithm::from_str("SHA-256")
273        );
274        assert_eq!(
275            Ok(Algorithm::new(AlgorithmType::SHA2_256, true)),
276            Algorithm::from_str("SHA-256-sess")
277        );
278        assert_eq!(
279            Ok(Algorithm::new(AlgorithmType::SHA2_512_256, false)),
280            Algorithm::from_str("SHA-512-256")
281        );
282        assert_eq!(
283            Ok(Algorithm::new(AlgorithmType::SHA2_512_256, true)),
284            Algorithm::from_str("SHA-512-256-sess")
285        );
286        assert_eq!(
287            Err(UnknownAlgorithm("OTHER_ALGORITHM".to_string())),
288            Algorithm::from_str("OTHER_ALGORITHM")
289        );
290
291        // String building
292        assert_eq!(
293            "MD5".to_string(),
294            Algorithm::new(AlgorithmType::MD5, false).to_string()
295        );
296        assert_eq!(
297            "MD5-sess".to_string(),
298            Algorithm::new(AlgorithmType::MD5, true).to_string()
299        );
300        assert_eq!(
301            "SHA-256".to_string(),
302            Algorithm::new(AlgorithmType::SHA2_256, false).to_string()
303        );
304        assert_eq!(
305            "SHA-256-sess".to_string(),
306            Algorithm::new(AlgorithmType::SHA2_256, true).to_string()
307        );
308        assert_eq!(
309            "SHA-512-256".to_string(),
310            Algorithm::new(AlgorithmType::SHA2_512_256, false).to_string()
311        );
312        assert_eq!(
313            "SHA-512-256-sess".to_string(),
314            Algorithm::new(AlgorithmType::SHA2_512_256, true).to_string()
315        );
316
317        // Default
318        assert_eq!(
319            Algorithm::new(AlgorithmType::MD5, false),
320            Default::default()
321        );
322
323        // Hash calculation
324        assert_eq!(
325            "e2fc714c4727ee9395f324cd2e7f331f".to_string(),
326            Algorithm::new(AlgorithmType::MD5, false).hash("abcd".as_bytes())
327        );
328
329        assert_eq!(
330            "e2fc714c4727ee9395f324cd2e7f331f".to_string(),
331            Algorithm::new(AlgorithmType::MD5, false).hash_str("abcd")
332        );
333
334        assert_eq!(
335            "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589".to_string(),
336            Algorithm::new(AlgorithmType::SHA2_256, false).hash("abcd".as_bytes())
337        );
338
339        assert_eq!(
340            "d2891c7978be0e24948f37caa415b87cb5cbe2b26b7bad9dc6391b8a6f6ddcc9".to_string(),
341            Algorithm::new(AlgorithmType::SHA2_512_256, false).hash("abcd".as_bytes())
342        );
343    }
344
345    #[test]
346    fn test_qop() {
347        assert_eq!(Ok(Qop::AUTH), Qop::from_str("auth"));
348        assert_eq!(Ok(Qop::AUTH_INT), Qop::from_str("auth-int"));
349        assert_eq!(Err(BadQop("banana".to_string())), Qop::from_str("banana"));
350
351        assert_eq!("auth".to_string(), Qop::AUTH.to_string());
352        assert_eq!("auth-int".to_string(), Qop::AUTH_INT.to_string());
353    }
354
355    #[test]
356    fn test_qop_algo() {
357        assert_eq!(Option::<Qop>::None, QopAlgo::NONE.into());
358        assert_eq!(Some(Qop::AUTH), QopAlgo::AUTH.into());
359        assert_eq!(
360            Some(Qop::AUTH_INT),
361            QopAlgo::AUTH_INT("foo".as_bytes()).into()
362        );
363    }
364
365    #[test]
366    fn test_charset() {
367        assert_eq!(Ok(Charset::UTF8), Charset::from_str("UTF-8"));
368        assert_eq!(Err(BadCharset("ASCII".into())), Charset::from_str("ASCII"));
369
370        assert_eq!("UTF-8".to_string(), Charset::UTF8.to_string());
371        assert_eq!("ASCII".to_string(), Charset::ASCII.to_string());
372    }
373
374    #[test]
375    fn test_http_method() {
376        // Well known 'static
377        assert_eq!(HttpMethod::GET, "GET".into());
378        assert_eq!(HttpMethod::POST, "POST".into());
379        assert_eq!(HttpMethod::PUT, "PUT".into());
380        assert_eq!(HttpMethod::DELETE, "DELETE".into());
381        assert_eq!(HttpMethod::HEAD, "HEAD".into());
382        assert_eq!(HttpMethod::OPTIONS, "OPTIONS".into());
383        assert_eq!(HttpMethod::CONNECT, "CONNECT".into());
384        assert_eq!(HttpMethod::PATCH, "PATCH".into());
385        assert_eq!(HttpMethod::TRACE, "TRACE".into());
386        // As bytes
387        assert_eq!(HttpMethod::GET, "GET".as_bytes().into());
388        assert_eq!(
389            HttpMethod(Cow::Borrowed("ěščř")),
390            "ěščř".as_bytes().into()
391        );
392        assert_eq!(
393            HttpMethod(Cow::Owned("AB�".to_string())), // Lossy conversion
394            (&[65u8, 66, 156][..]).into()
395        );
396        // Well known String
397        assert_eq!(HttpMethod::GET, String::from("GET").into());
398        // Custom String
399        assert_eq!(
400            HttpMethod(Cow::Borrowed("NonsenseMethod")),
401            "NonsenseMethod".into()
402        );
403        assert_eq!(
404            HttpMethod(Cow::Owned("NonsenseMethod".to_string())),
405            "NonsenseMethod".to_string().into()
406        );
407        // Custom Cow
408        assert_eq!(HttpMethod::HEAD, Cow::Borrowed("HEAD").into());
409        assert_eq!(
410            HttpMethod(Cow::Borrowed("NonsenseMethod")),
411            Cow::Borrowed("NonsenseMethod").into()
412        );
413        // to string
414        assert_eq!("GET".to_string(), HttpMethod::GET.to_string());
415        assert_eq!("POST".to_string(), HttpMethod::POST.to_string());
416        assert_eq!("PUT".to_string(), HttpMethod::PUT.to_string());
417        assert_eq!("DELETE".to_string(), HttpMethod::DELETE.to_string());
418        assert_eq!("HEAD".to_string(), HttpMethod::HEAD.to_string());
419        assert_eq!("OPTIONS".to_string(), HttpMethod::OPTIONS.to_string());
420        assert_eq!("CONNECT".to_string(), HttpMethod::CONNECT.to_string());
421        assert_eq!("PATCH".to_string(), HttpMethod::PATCH.to_string());
422        assert_eq!("TRACE".to_string(), HttpMethod::TRACE.to_string());
423
424        assert_eq!(
425            "NonsenseMethod".to_string(),
426            HttpMethod(Cow::Borrowed("NonsenseMethod")).to_string()
427        );
428        assert_eq!(
429            "NonsenseMethod".to_string(),
430            HttpMethod(Cow::Owned("NonsenseMethod".to_string())).to_string()
431        );
432    }
433
434    #[cfg(feature = "http")]
435    #[test]
436    fn test_http_crate() {
437        assert_eq!(HttpMethod::GET, http::Method::GET.clone().into());
438        assert_eq!(
439            HttpMethod(Cow::Owned("BANANA".to_string())),
440            http::Method::from_str("BANANA").unwrap().into()
441        );
442
443        assert_eq!(HttpMethod::GET, (&http::Method::GET).into());
444        let x = http::Method::from_str("BANANA").unwrap();
445        assert_eq!(
446            HttpMethod(Cow::Borrowed("BANANA")),
447            (&x).into()
448        );
449    }
450}