Browse Source

improvements around HttpMethod, more tests

Ondřej Hruška 3 months ago
parent
commit
bae4c13597
Signed by: Ondřej Hruška <ondra@ondrovo.com> GPG key ID: 2C5FD5035250423D
5 changed files with 277 additions and 25 deletions
  1. 7 0
      CHANGELOG.md
  2. 8 1
      Cargo.toml
  3. 239 6
      src/enums.rs
  4. 1 1
      src/error.rs
  5. 22 17
      src/lib.rs

+ 7 - 0
CHANGELOG.md View File

@@ -1,3 +1,10 @@
1
+# 0.3.0
2
+
3
+- Added lifetime parameter to `HttpMethod`
4
+- Changed `HttpMethod::OTHER(&'static str)` to `HttpMethod::OTHER(Cow<'a, str>)`
5
+- Added unit tests
6
+- Converted one Into impl to From
7
+
1 8
 # 0.2.4
2 9
 
3 10
 - Update dependencies

+ 8 - 1
Cargo.toml View File

@@ -1,6 +1,6 @@
1 1
 [package]
2 2
 name = "digest_auth"
3
-version = "0.2.4"
3
+version = "0.3.0"
4 4
 authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
5 5
 edition = "2018"
6 6
 description = "Implementation of the Digest Auth algorithm as defined in IETF RFC 2069, 2617, and 7616, intended for HTTP clients"
@@ -20,3 +20,10 @@ hex = "0.4"
20 20
 sha2 = "0.9"
21 21
 md-5 = "0.9"
22 22
 digest = "0.9"
23
+
24
+[dependencies.http]
25
+version = "0.2.4"
26
+optional = true
27
+
28
+[features]
29
+default = []

+ 239 - 6
src/enums.rs View File

@@ -1,3 +1,5 @@
1
+#![allow(clippy::upper_case_acronyms)]
2
+
1 3
 use crate::{Error, Error::*, Result};
2 4
 use std::fmt;
3 5
 use std::fmt::{Display, Formatter};
@@ -6,6 +8,7 @@ use std::str::FromStr;
6 8
 use digest::{Digest, DynDigest};
7 9
 use md5::Md5;
8 10
 use sha2::{Sha256, Sha512Trunc256};
11
+use std::borrow::Cow;
9 12
 
10 13
 /// Algorithm type
11 14
 #[derive(Debug, PartialEq, Clone, Copy)]
@@ -127,10 +130,9 @@ pub enum QopAlgo<'a> {
127 130
 }
128 131
 
129 132
 // casting back...
130
-impl<'a> Into<Option<Qop>> for QopAlgo<'a> {
131
-    /// Convert to ?Qop
132
-    fn into(self) -> Option<Qop> {
133
-        match self {
133
+impl<'a> From<QopAlgo<'a>> for Option<Qop> {
134
+    fn from(algo: QopAlgo<'a>) -> Self {
135
+        match algo {
134 136
             QopAlgo::NONE => None,
135 137
             QopAlgo::AUTH => Some(Qop::AUTH),
136 138
             QopAlgo::AUTH_INT(_) => Some(Qop::AUTH_INT),
@@ -167,12 +169,12 @@ impl Display for Charset {
167 169
 }
168 170
 
169 171
 /// HTTP method (used when generating the response hash for some Qop options)
170
-#[derive(Debug)]
172
+#[derive(Debug, PartialEq, Clone)]
171 173
 pub enum HttpMethod<'a> {
172 174
     GET,
173 175
     POST,
174 176
     HEAD,
175
-    OTHER(&'a str),
177
+    OTHER(Cow<'a, str>),
176 178
 }
177 179
 
178 180
 impl<'a> Default for HttpMethod<'a> {
@@ -192,3 +194,234 @@ impl<'a> Display for HttpMethod<'a> {
192 194
         })
193 195
     }
194 196
 }
197
+
198
+impl<'a> From<&'a str> for HttpMethod<'a> {
199
+    fn from(s: &'a str) -> Self {
200
+        match s {
201
+            "GET" => HttpMethod::GET,
202
+            "POST" => HttpMethod::POST,
203
+            "HEAD" => HttpMethod::HEAD,
204
+            s => HttpMethod::OTHER(Cow::Borrowed(s)),
205
+        }
206
+    }
207
+}
208
+
209
+impl<'a> From<&'a [u8]> for HttpMethod<'a> {
210
+    fn from(s: &'a [u8]) -> Self {
211
+        String::from_utf8_lossy(s).into()
212
+    }
213
+}
214
+
215
+impl<'a> From<String> for HttpMethod<'a> {
216
+    fn from(s: String) -> Self {
217
+        match &s[..] {
218
+            "GET" => HttpMethod::GET,
219
+            "POST" => HttpMethod::POST,
220
+            "HEAD" => HttpMethod::HEAD,
221
+            _ => HttpMethod::OTHER(Cow::Owned(s)),
222
+        }
223
+    }
224
+}
225
+
226
+impl<'a> From<Cow<'a, str>> for HttpMethod<'a> {
227
+    fn from(s: Cow<'a, str>) -> Self {
228
+        match &s[..] {
229
+            "GET" => HttpMethod::GET,
230
+            "POST" => HttpMethod::POST,
231
+            "HEAD" => HttpMethod::HEAD,
232
+            _ => HttpMethod::OTHER(s),
233
+        }
234
+    }
235
+}
236
+
237
+#[cfg(feature = "http")]
238
+impl From<http::Method> for HttpMethod<'static> {
239
+    fn from(method: http::Method) -> Self {
240
+        match method {
241
+            http::Method::GET => HttpMethod::GET,
242
+            http::Method::POST => HttpMethod::POST,
243
+            http::Method::HEAD => HttpMethod::HEAD,
244
+            other => HttpMethod::OTHER(other.to_string().into()),
245
+        }
246
+    }
247
+}
248
+
249
+#[cfg(test)]
250
+mod test {
251
+    use crate::error::Error::{BadCharset, BadQop, UnknownAlgorithm};
252
+    use crate::{Algorithm, AlgorithmType, Charset, HttpMethod, Qop, QopAlgo};
253
+    use std::borrow::Cow;
254
+    use std::str::FromStr;
255
+
256
+    #[test]
257
+    fn test_algorithm_type() {
258
+        // String parsing
259
+        assert_eq!(
260
+            Ok(Algorithm::new(AlgorithmType::MD5, false)),
261
+            Algorithm::from_str("MD5")
262
+        );
263
+        assert_eq!(
264
+            Ok(Algorithm::new(AlgorithmType::MD5, true)),
265
+            Algorithm::from_str("MD5-sess")
266
+        );
267
+        assert_eq!(
268
+            Ok(Algorithm::new(AlgorithmType::SHA2_256, false)),
269
+            Algorithm::from_str("SHA-256")
270
+        );
271
+        assert_eq!(
272
+            Ok(Algorithm::new(AlgorithmType::SHA2_256, true)),
273
+            Algorithm::from_str("SHA-256-sess")
274
+        );
275
+        assert_eq!(
276
+            Ok(Algorithm::new(AlgorithmType::SHA2_512_256, false)),
277
+            Algorithm::from_str("SHA-512-256")
278
+        );
279
+        assert_eq!(
280
+            Ok(Algorithm::new(AlgorithmType::SHA2_512_256, true)),
281
+            Algorithm::from_str("SHA-512-256-sess")
282
+        );
283
+        assert_eq!(
284
+            Err(UnknownAlgorithm("OTHER_ALGORITHM".to_string())),
285
+            Algorithm::from_str("OTHER_ALGORITHM")
286
+        );
287
+
288
+        // String building
289
+        assert_eq!(
290
+            "MD5".to_string(),
291
+            Algorithm::new(AlgorithmType::MD5, false).to_string()
292
+        );
293
+        assert_eq!(
294
+            "MD5-sess".to_string(),
295
+            Algorithm::new(AlgorithmType::MD5, true).to_string()
296
+        );
297
+        assert_eq!(
298
+            "SHA-256".to_string(),
299
+            Algorithm::new(AlgorithmType::SHA2_256, false).to_string()
300
+        );
301
+        assert_eq!(
302
+            "SHA-256-sess".to_string(),
303
+            Algorithm::new(AlgorithmType::SHA2_256, true).to_string()
304
+        );
305
+        assert_eq!(
306
+            "SHA-512-256".to_string(),
307
+            Algorithm::new(AlgorithmType::SHA2_512_256, false).to_string()
308
+        );
309
+        assert_eq!(
310
+            "SHA-512-256-sess".to_string(),
311
+            Algorithm::new(AlgorithmType::SHA2_512_256, true).to_string()
312
+        );
313
+
314
+        // Default
315
+        assert_eq!(
316
+            Algorithm::new(AlgorithmType::MD5, false),
317
+            Default::default()
318
+        );
319
+
320
+        // Hash calculation
321
+        assert_eq!(
322
+            "e2fc714c4727ee9395f324cd2e7f331f".to_string(),
323
+            Algorithm::new(AlgorithmType::MD5, false).hash("abcd".as_bytes())
324
+        );
325
+
326
+        assert_eq!(
327
+            "e2fc714c4727ee9395f324cd2e7f331f".to_string(),
328
+            Algorithm::new(AlgorithmType::MD5, false).hash_str("abcd")
329
+        );
330
+
331
+        assert_eq!(
332
+            "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589".to_string(),
333
+            Algorithm::new(AlgorithmType::SHA2_256, false).hash("abcd".as_bytes())
334
+        );
335
+
336
+        assert_eq!(
337
+            "d2891c7978be0e24948f37caa415b87cb5cbe2b26b7bad9dc6391b8a6f6ddcc9".to_string(),
338
+            Algorithm::new(AlgorithmType::SHA2_512_256, false).hash("abcd".as_bytes())
339
+        );
340
+    }
341
+
342
+    #[test]
343
+    fn test_qop() {
344
+        assert_eq!(Ok(Qop::AUTH), Qop::from_str("auth"));
345
+        assert_eq!(Ok(Qop::AUTH_INT), Qop::from_str("auth-int"));
346
+        assert_eq!(Err(BadQop("banana".to_string())), Qop::from_str("banana"));
347
+
348
+        assert_eq!("auth".to_string(), Qop::AUTH.to_string());
349
+        assert_eq!("auth-int".to_string(), Qop::AUTH_INT.to_string());
350
+    }
351
+
352
+    #[test]
353
+    fn test_qop_algo() {
354
+        assert_eq!(Option::<Qop>::None, QopAlgo::NONE.into());
355
+        assert_eq!(Some(Qop::AUTH), QopAlgo::AUTH.into());
356
+        assert_eq!(
357
+            Some(Qop::AUTH_INT),
358
+            QopAlgo::AUTH_INT("foo".as_bytes()).into()
359
+        );
360
+    }
361
+
362
+    #[test]
363
+    fn test_charset() {
364
+        assert_eq!(Ok(Charset::UTF8), Charset::from_str("UTF-8"));
365
+        assert_eq!(Err(BadCharset("ASCII".into())), Charset::from_str("ASCII"));
366
+
367
+        assert_eq!("UTF-8".to_string(), Charset::UTF8.to_string());
368
+        assert_eq!("ASCII".to_string(), Charset::ASCII.to_string());
369
+    }
370
+
371
+    #[test]
372
+    fn test_http_method() {
373
+        // Well known 'static
374
+        assert_eq!(HttpMethod::GET, "GET".into());
375
+        assert_eq!(HttpMethod::POST, "POST".into());
376
+        assert_eq!(HttpMethod::HEAD, "HEAD".into());
377
+        // As bytes
378
+        assert_eq!(HttpMethod::GET, "GET".as_bytes().into());
379
+        assert_eq!(
380
+            HttpMethod::OTHER(Cow::Borrowed("ěščř")),
381
+            "ěščř".as_bytes().into()
382
+        );
383
+        assert_eq!(
384
+            HttpMethod::OTHER(Cow::Owned("AB�".to_string())),
385
+            (&[65u8, 66, 156][..]).into()
386
+        );
387
+        // Well known String
388
+        assert_eq!(HttpMethod::GET, String::from("GET").into());
389
+        // Custom String
390
+        assert_eq!(
391
+            HttpMethod::OTHER(Cow::Borrowed("NonsenseMethod")),
392
+            "NonsenseMethod".into()
393
+        );
394
+        assert_eq!(
395
+            HttpMethod::OTHER(Cow::Owned("NonsenseMethod".to_string())),
396
+            "NonsenseMethod".to_string().into()
397
+        );
398
+        // Custom Cow
399
+        assert_eq!(HttpMethod::HEAD, Cow::Borrowed("HEAD").into());
400
+        assert_eq!(
401
+            HttpMethod::OTHER(Cow::Borrowed("NonsenseMethod")),
402
+            Cow::Borrowed("NonsenseMethod").into()
403
+        );
404
+        // to string
405
+        assert_eq!("GET".to_string(), HttpMethod::GET.to_string());
406
+        assert_eq!("POST".to_string(), HttpMethod::POST.to_string());
407
+        assert_eq!("HEAD".to_string(), HttpMethod::HEAD.to_string());
408
+        assert_eq!(
409
+            "NonsenseMethod".to_string(),
410
+            HttpMethod::OTHER(Cow::Borrowed("NonsenseMethod")).to_string()
411
+        );
412
+        assert_eq!(
413
+            "NonsenseMethod".to_string(),
414
+            HttpMethod::OTHER(Cow::Owned("NonsenseMethod".to_string())).to_string()
415
+        );
416
+    }
417
+
418
+    #[cfg(feature = "http")]
419
+    #[test]
420
+    fn test_http_crate() {
421
+        assert_eq!(HttpMethod::GET, http::Method::GET.into());
422
+        assert_eq!(
423
+            HttpMethod::OTHER(Cow::Owned("BANANA".to_string())),
424
+            http::Method::from_str("BANANA").unwrap().into()
425
+        );
426
+    }
427
+}

+ 1 - 1
src/error.rs View File

@@ -1,7 +1,7 @@
1 1
 use std::fmt::{self, Display, Formatter};
2 2
 use std::result;
3 3
 
4
-#[derive(Debug)]
4
+#[derive(Debug, PartialEq)]
5 5
 pub enum Error {
6 6
     BadCharset(String),
7 7
     UnknownAlgorithm(String),

+ 22 - 17
src/lib.rs View File

@@ -62,9 +62,13 @@ pub fn parse(www_authorize: &str) -> Result<WwwAuthenticateHeader> {
62 62
     WwwAuthenticateHeader::parse(www_authorize)
63 63
 }
64 64
 
65
-#[test]
66
-fn test_parse_respond() {
67
-    let src = r#"
65
+#[cfg(test)]
66
+mod test {
67
+    use crate::{AuthContext, Error};
68
+
69
+    #[test]
70
+    fn test_parse_respond() {
71
+        let src = r#"
68 72
     Digest
69 73
        realm="http-auth@example.org",
70 74
        qop="auth, auth-int",
@@ -73,17 +77,17 @@ fn test_parse_respond() {
73 77
        opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
74 78
     "#;
75 79
 
76
-    let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
77
-    context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
80
+        let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
81
+        context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
78 82
 
79
-    let mut prompt = crate::parse(src).unwrap();
80
-    let answer = prompt.respond(&context).unwrap();
83
+        let mut prompt = crate::parse(src).unwrap();
84
+        let answer = prompt.respond(&context).unwrap();
81 85
 
82
-    let str = answer.to_string().replace(", ", ",\n  ");
86
+        let str = answer.to_string().replace(", ", ",\n  ");
83 87
 
84
-    assert_eq!(
85
-        str,
86
-        r#"
88
+        assert_eq!(
89
+            str,
90
+            r#"
87 91
 Digest username="Mufasa",
88 92
   realm="http-auth@example.org",
89 93
   nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
@@ -95,11 +99,12 @@ Digest username="Mufasa",
95 99
   opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
96 100
   algorithm=MD5
97 101
 "#
98
-        .trim()
99
-    );
100
-}
102
+            .trim()
103
+        );
104
+    }
101 105
 
102
-#[test]
103
-fn test_cast_error() {
104
-    let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into();
106
+    #[test]
107
+    fn test_cast_error() {
108
+        let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into();
109
+    }
105 110
 }