improvements around HttpMethod, more tests

pull/6/head
Ondřej Hruška 3 年前
父節點 a1f3c6ebfb
當前提交 bae4c13597
簽署人: MightyPork
GPG 金鑰 ID: 2C5FD5035250423D
  1. 7
      CHANGELOG.md
  2. 9
      Cargo.toml
  3. 245
      src/enums.rs
  4. 2
      src/error.rs
  5. 39
      src/lib.rs

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

@ -1,6 +1,6 @@
[package]
name = "digest_auth"
version = "0.2.4"
version = "0.3.0"
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
edition = "2018"
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"
sha2 = "0.9"
md-5 = "0.9"
digest = "0.9"
[dependencies.http]
version = "0.2.4"
optional = true
[features]
default = []

@ -1,3 +1,5 @@
#![allow(clippy::upper_case_acronyms)]
use crate::{Error, Error::*, Result};
use std::fmt;
use std::fmt::{Display, Formatter};
@ -6,6 +8,7 @@ use std::str::FromStr;
use digest::{Digest, DynDigest};
use md5::Md5;
use sha2::{Sha256, Sha512Trunc256};
use std::borrow::Cow;
/// Algorithm type
#[derive(Debug, PartialEq, Clone, Copy)]
@ -127,10 +130,9 @@ pub enum QopAlgo<'a> {
}
// casting back...
impl<'a> Into<Option<Qop>> for QopAlgo<'a> {
/// Convert to ?Qop
fn into(self) -> Option<Qop> {
match self {
impl<'a> From<QopAlgo<'a>> for Option<Qop> {
fn from(algo: QopAlgo<'a>) -> Self {
match algo {
QopAlgo::NONE => None,
QopAlgo::AUTH => Some(Qop::AUTH),
QopAlgo::AUTH_INT(_) => Some(Qop::AUTH_INT),
@ -167,12 +169,12 @@ impl Display for Charset {
}
/// HTTP method (used when generating the response hash for some Qop options)
#[derive(Debug)]
#[derive(Debug, PartialEq, Clone)]
pub enum HttpMethod<'a> {
GET,
POST,
HEAD,
OTHER(&'a str),
OTHER(Cow<'a, str>),
}
impl<'a> Default for HttpMethod<'a> {
@ -192,3 +194,234 @@ impl<'a> Display for HttpMethod<'a> {
})
}
}
impl<'a> From<&'a str> for HttpMethod<'a> {
fn from(s: &'a str) -> Self {
match s {
"GET" => HttpMethod::GET,
"POST" => HttpMethod::POST,
"HEAD" => HttpMethod::HEAD,
s => HttpMethod::OTHER(Cow::Borrowed(s)),
}
}
}
impl<'a> From<&'a [u8]> for HttpMethod<'a> {
fn from(s: &'a [u8]) -> Self {
String::from_utf8_lossy(s).into()
}
}
impl<'a> From<String> for HttpMethod<'a> {
fn from(s: String) -> Self {
match &s[..] {
"GET" => HttpMethod::GET,
"POST" => HttpMethod::POST,
"HEAD" => HttpMethod::HEAD,
_ => HttpMethod::OTHER(Cow::Owned(s)),
}
}
}
impl<'a> From<Cow<'a, str>> for HttpMethod<'a> {
fn from(s: Cow<'a, str>) -> Self {
match &s[..] {
"GET" => HttpMethod::GET,
"POST" => HttpMethod::POST,
"HEAD" => HttpMethod::HEAD,
_ => HttpMethod::OTHER(s),
}
}
}
#[cfg(feature = "http")]
impl From<http::Method> for HttpMethod<'static> {
fn from(method: http::Method) -> Self {
match method {
http::Method::GET => HttpMethod::GET,
http::Method::POST => HttpMethod::POST,
http::Method::HEAD => HttpMethod::HEAD,
other => HttpMethod::OTHER(other.to_string().into()),
}
}
}
#[cfg(test)]
mod test {
use crate::error::Error::{BadCharset, BadQop, UnknownAlgorithm};
use crate::{Algorithm, AlgorithmType, Charset, HttpMethod, Qop, QopAlgo};
use std::borrow::Cow;
use std::str::FromStr;
#[test]
fn test_algorithm_type() {
// String parsing
assert_eq!(
Ok(Algorithm::new(AlgorithmType::MD5, false)),
Algorithm::from_str("MD5")
);
assert_eq!(
Ok(Algorithm::new(AlgorithmType::MD5, true)),
Algorithm::from_str("MD5-sess")
);
assert_eq!(
Ok(Algorithm::new(AlgorithmType::SHA2_256, false)),
Algorithm::from_str("SHA-256")
);
assert_eq!(
Ok(Algorithm::new(AlgorithmType::SHA2_256, true)),
Algorithm::from_str("SHA-256-sess")
);
assert_eq!(
Ok(Algorithm::new(AlgorithmType::SHA2_512_256, false)),
Algorithm::from_str("SHA-512-256")
);
assert_eq!(
Ok(Algorithm::new(AlgorithmType::SHA2_512_256, true)),
Algorithm::from_str("SHA-512-256-sess")
);
assert_eq!(
Err(UnknownAlgorithm("OTHER_ALGORITHM".to_string())),
Algorithm::from_str("OTHER_ALGORITHM")
);
// String building
assert_eq!(
"MD5".to_string(),
Algorithm::new(AlgorithmType::MD5, false).to_string()
);
assert_eq!(
"MD5-sess".to_string(),
Algorithm::new(AlgorithmType::MD5, true).to_string()
);
assert_eq!(
"SHA-256".to_string(),
Algorithm::new(AlgorithmType::SHA2_256, false).to_string()
);
assert_eq!(
"SHA-256-sess".to_string(),
Algorithm::new(AlgorithmType::SHA2_256, true).to_string()
);
assert_eq!(
"SHA-512-256".to_string(),
Algorithm::new(AlgorithmType::SHA2_512_256, false).to_string()
);
assert_eq!(
"SHA-512-256-sess".to_string(),
Algorithm::new(AlgorithmType::SHA2_512_256, true).to_string()
);
// Default
assert_eq!(
Algorithm::new(AlgorithmType::MD5, false),
Default::default()
);
// Hash calculation
assert_eq!(
"e2fc714c4727ee9395f324cd2e7f331f".to_string(),
Algorithm::new(AlgorithmType::MD5, false).hash("abcd".as_bytes())
);
assert_eq!(
"e2fc714c4727ee9395f324cd2e7f331f".to_string(),
Algorithm::new(AlgorithmType::MD5, false).hash_str("abcd")
);
assert_eq!(
"88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589".to_string(),
Algorithm::new(AlgorithmType::SHA2_256, false).hash("abcd".as_bytes())
);
assert_eq!(
"d2891c7978be0e24948f37caa415b87cb5cbe2b26b7bad9dc6391b8a6f6ddcc9".to_string(),
Algorithm::new(AlgorithmType::SHA2_512_256, false).hash("abcd".as_bytes())
);
}
#[test]
fn test_qop() {
assert_eq!(Ok(Qop::AUTH), Qop::from_str("auth"));
assert_eq!(Ok(Qop::AUTH_INT), Qop::from_str("auth-int"));
assert_eq!(Err(BadQop("banana".to_string())), Qop::from_str("banana"));
assert_eq!("auth".to_string(), Qop::AUTH.to_string());
assert_eq!("auth-int".to_string(), Qop::AUTH_INT.to_string());
}
#[test]
fn test_qop_algo() {
assert_eq!(Option::<Qop>::None, QopAlgo::NONE.into());
assert_eq!(Some(Qop::AUTH), QopAlgo::AUTH.into());
assert_eq!(
Some(Qop::AUTH_INT),
QopAlgo::AUTH_INT("foo".as_bytes()).into()
);
}
#[test]
fn test_charset() {
assert_eq!(Ok(Charset::UTF8), Charset::from_str("UTF-8"));
assert_eq!(Err(BadCharset("ASCII".into())), Charset::from_str("ASCII"));
assert_eq!("UTF-8".to_string(), Charset::UTF8.to_string());
assert_eq!("ASCII".to_string(), Charset::ASCII.to_string());
}
#[test]
fn test_http_method() {
// Well known 'static
assert_eq!(HttpMethod::GET, "GET".into());
assert_eq!(HttpMethod::POST, "POST".into());
assert_eq!(HttpMethod::HEAD, "HEAD".into());
// As bytes
assert_eq!(HttpMethod::GET, "GET".as_bytes().into());
assert_eq!(
HttpMethod::OTHER(Cow::Borrowed("ěščř")),
"ěščř".as_bytes().into()
);
assert_eq!(
HttpMethod::OTHER(Cow::Owned("AB<EFBFBD>".to_string())),
(&[65u8, 66, 156][..]).into()
);
// Well known String
assert_eq!(HttpMethod::GET, String::from("GET").into());
// Custom String
assert_eq!(
HttpMethod::OTHER(Cow::Borrowed("NonsenseMethod")),
"NonsenseMethod".into()
);
assert_eq!(
HttpMethod::OTHER(Cow::Owned("NonsenseMethod".to_string())),
"NonsenseMethod".to_string().into()
);
// Custom Cow
assert_eq!(HttpMethod::HEAD, Cow::Borrowed("HEAD").into());
assert_eq!(
HttpMethod::OTHER(Cow::Borrowed("NonsenseMethod")),
Cow::Borrowed("NonsenseMethod").into()
);
// to string
assert_eq!("GET".to_string(), HttpMethod::GET.to_string());
assert_eq!("POST".to_string(), HttpMethod::POST.to_string());
assert_eq!("HEAD".to_string(), HttpMethod::HEAD.to_string());
assert_eq!(
"NonsenseMethod".to_string(),
HttpMethod::OTHER(Cow::Borrowed("NonsenseMethod")).to_string()
);
assert_eq!(
"NonsenseMethod".to_string(),
HttpMethod::OTHER(Cow::Owned("NonsenseMethod".to_string())).to_string()
);
}
#[cfg(feature = "http")]
#[test]
fn test_http_crate() {
assert_eq!(HttpMethod::GET, http::Method::GET.into());
assert_eq!(
HttpMethod::OTHER(Cow::Owned("BANANA".to_string())),
http::Method::from_str("BANANA").unwrap().into()
);
}
}

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

@ -62,9 +62,13 @@ pub fn parse(www_authorize: &str) -> Result<WwwAuthenticateHeader> {
WwwAuthenticateHeader::parse(www_authorize)
}
#[test]
fn test_parse_respond() {
let src = r#"
#[cfg(test)]
mod test {
use crate::{AuthContext, Error};
#[test]
fn test_parse_respond() {
let src = r#"
Digest
realm="http-auth@example.org",
qop="auth, auth-int",
@ -73,17 +77,17 @@ fn test_parse_respond() {
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
"#;
let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
let mut prompt = crate::parse(src).unwrap();
let answer = prompt.respond(&context).unwrap();
let mut prompt = crate::parse(src).unwrap();
let answer = prompt.respond(&context).unwrap();
let str = answer.to_string().replace(", ", ",\n ");
let str = answer.to_string().replace(", ", ",\n ");
assert_eq!(
str,
r#"
assert_eq!(
str,
r#"
Digest username="Mufasa",
realm="http-auth@example.org",
nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
@ -95,11 +99,12 @@ Digest username="Mufasa",
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
algorithm=MD5
"#
.trim()
);
}
.trim()
);
}
#[test]
fn test_cast_error() {
let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into();
#[test]
fn test_cast_error() {
let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into();
}
}

載入中…
取消
儲存