Compare commits

..

No commits in common. 'master' and 'v0.2.4' have entirely different histories.

  1. 15
      CHANGELOG.md
  2. 15
      Cargo.toml
  3. 2
      README.md
  4. 4
      src/digest.rs
  5. 296
      src/enums.rs
  6. 2
      src/error.rs
  7. 39
      src/lib.rs

@ -1,15 +0,0 @@
# 0.3.1
- Update deps
# 0.3.0
- Added lifetime parameter to `HttpMethod`
- Changed `HttpMethod` to `HttpMethod<'a>(Cow<'a, str>)`
- Added unit tests
- Added support for the `http` crate's `Method` struct (optional feature)
# 0.2.4
- Update dependencies

@ -1,6 +1,6 @@
[package]
name = "digest_auth"
version = "0.3.1"
version = "0.2.4"
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"
@ -17,13 +17,6 @@ license = "MIT"
[dependencies]
rand = "0.8"
hex = "0.4"
sha2 = "0.10"
md-5 = "0.10"
digest = "0.10"
[dependencies.http]
version = "0.2.4"
optional = true
[features]
default = []
sha2 = "0.9"
md-5 = "0.9"
digest = "0.9"

@ -2,5 +2,3 @@ Rust implementation of Digest Auth hashing algorithms,
as defined in IETF RFC 2069, 2617, and 7616.
This crate provides the authentication header parsing and generation code.
Please see the docs and tests for examples.

@ -161,7 +161,7 @@ pub struct AuthContext<'a> {
/// May be left out if not using auth-int
pub body: Option<Cow<'a, [u8]>>,
/// HTTP method used (defaults to GET)
pub method: HttpMethod<'a>,
pub method: HttpMethod,
/// Spoofed client nonce (use only for tests; a random nonce is generated automatically)
pub cnonce: Option<Cow<'a, str>>,
}
@ -202,7 +202,7 @@ impl<'a> AuthContext<'a> {
password: PW,
uri: UR,
body: Option<BD>,
method: HttpMethod<'a>,
method: HttpMethod,
) -> Self
where
UN: Into<Cow<'a, str>>,

@ -1,5 +1,3 @@
#![allow(clippy::upper_case_acronyms)]
use crate::{Error, Error::*, Result};
use std::fmt;
use std::fmt::{Display, Formatter};
@ -7,8 +5,7 @@ use std::str::FromStr;
use digest::{Digest, DynDigest};
use md5::Md5;
use sha2::{Sha256, Sha512_256};
use std::borrow::Cow;
use sha2::{Sha256, Sha512Trunc256};
/// Algorithm type
#[derive(Debug, PartialEq, Clone, Copy)]
@ -37,7 +34,7 @@ impl Algorithm {
let mut hash: Box<dyn DynDigest> = match self.algo {
AlgorithmType::MD5 => Box::new(Md5::new()),
AlgorithmType::SHA2_256 => Box::new(Sha256::new()),
AlgorithmType::SHA2_512_256 => Box::new(Sha512_256::new()),
AlgorithmType::SHA2_512_256 => Box::new(Sha512Trunc256::new()),
};
hash.update(bytes);
@ -130,9 +127,10 @@ pub enum QopAlgo<'a> {
}
// casting back...
impl<'a> From<QopAlgo<'a>> for Option<Qop> {
fn from(algo: QopAlgo<'a>) -> Self {
match algo {
impl<'a> Into<Option<Qop>> for QopAlgo<'a> {
/// Convert to ?Qop
fn into(self) -> Option<Qop> {
match self {
QopAlgo::NONE => None,
QopAlgo::AUTH => Some(Qop::AUTH),
QopAlgo::AUTH_INT(_) => Some(Qop::AUTH_INT),
@ -169,282 +167,28 @@ impl Display for Charset {
}
/// HTTP method (used when generating the response hash for some Qop options)
#[derive(Debug, PartialEq, Clone)]
pub struct HttpMethod<'a>(pub Cow<'a, str>);
// Well-known methods are provided as convenient associated constants
impl<'a> HttpMethod<'a> {
pub const GET : Self = HttpMethod(Cow::Borrowed("GET"));
pub const POST : Self = HttpMethod(Cow::Borrowed("POST"));
pub const PUT : Self = HttpMethod(Cow::Borrowed("PUT"));
pub const DELETE : Self = HttpMethod(Cow::Borrowed("DELETE"));
pub const HEAD : Self = HttpMethod(Cow::Borrowed("HEAD"));
pub const OPTIONS : Self = HttpMethod(Cow::Borrowed("OPTIONS"));
pub const CONNECT : Self = HttpMethod(Cow::Borrowed("CONNECT"));
pub const PATCH : Self = HttpMethod(Cow::Borrowed("PATCH"));
pub const TRACE : Self = HttpMethod(Cow::Borrowed("TRACE"));
#[derive(Debug)]
pub enum HttpMethod {
GET,
POST,
HEAD,
OTHER(&'static str),
}
impl<'a> Default for HttpMethod<'a> {
impl Default for HttpMethod {
fn default() -> Self {
HttpMethod::GET
}
}
impl<'a> Display for HttpMethod<'a> {
impl Display for HttpMethod {
/// Convert to uppercase string
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
impl<'a> From<&'a str> for HttpMethod<'a> {
fn from(s: &'a str) -> Self {
Self(s.into())
}
}
impl<'a> From<&'a [u8]> for HttpMethod<'a> {
fn from(s: &'a [u8]) -> Self {
Self(String::from_utf8_lossy(s).into())
}
}
impl<'a> From<String> for HttpMethod<'a> {
fn from(s: String) -> Self {
Self(s.into())
}
}
impl<'a> From<Cow<'a, str>> for HttpMethod<'a> {
fn from(s: Cow<'a, str>) -> Self {
Self(s)
}
}
#[cfg(feature = "http")]
impl From<http::Method> for HttpMethod<'static> {
fn from(method: http::Method) -> Self {
match method.as_str() {
// Avoid cloning when possible
"GET" => Self::GET,
"POST" => Self::POST,
"PUT" => Self::PUT,
"DELETE" => Self::DELETE,
"HEAD" => Self::HEAD,
"OPTIONS" => Self::OPTIONS,
"CONNECT" => Self::CONNECT,
"PATCH" => Self::PATCH,
"TRACE" => Self::TRACE,
// Clone custom strings. This is inefficient, but the inner string is private
other => Self(other.to_owned().into())
}
}
}
#[cfg(feature = "http")]
impl<'a> From<&'a http::Method> for HttpMethod<'a> {
fn from(method: &'a http::Method) -> HttpMethod<'a> {
Self(method.as_str().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::PUT, "PUT".into());
assert_eq!(HttpMethod::DELETE, "DELETE".into());
assert_eq!(HttpMethod::HEAD, "HEAD".into());
assert_eq!(HttpMethod::OPTIONS, "OPTIONS".into());
assert_eq!(HttpMethod::CONNECT, "CONNECT".into());
assert_eq!(HttpMethod::PATCH, "PATCH".into());
assert_eq!(HttpMethod::TRACE, "TRACE".into());
// As bytes
assert_eq!(HttpMethod::GET, "GET".as_bytes().into());
assert_eq!(
HttpMethod(Cow::Borrowed("ěščř")),
"ěščř".as_bytes().into()
);
assert_eq!(
HttpMethod(Cow::Owned("AB<EFBFBD>".to_string())), // Lossy conversion
(&[65u8, 66, 156][..]).into()
);
// Well known String
assert_eq!(HttpMethod::GET, String::from("GET").into());
// Custom String
assert_eq!(
HttpMethod(Cow::Borrowed("NonsenseMethod")),
"NonsenseMethod".into()
);
assert_eq!(
HttpMethod(Cow::Owned("NonsenseMethod".to_string())),
"NonsenseMethod".to_string().into()
);
// Custom Cow
assert_eq!(HttpMethod::HEAD, Cow::Borrowed("HEAD").into());
assert_eq!(
HttpMethod(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!("PUT".to_string(), HttpMethod::PUT.to_string());
assert_eq!("DELETE".to_string(), HttpMethod::DELETE.to_string());
assert_eq!("HEAD".to_string(), HttpMethod::HEAD.to_string());
assert_eq!("OPTIONS".to_string(), HttpMethod::OPTIONS.to_string());
assert_eq!("CONNECT".to_string(), HttpMethod::CONNECT.to_string());
assert_eq!("PATCH".to_string(), HttpMethod::PATCH.to_string());
assert_eq!("TRACE".to_string(), HttpMethod::TRACE.to_string());
assert_eq!(
"NonsenseMethod".to_string(),
HttpMethod(Cow::Borrowed("NonsenseMethod")).to_string()
);
assert_eq!(
"NonsenseMethod".to_string(),
HttpMethod(Cow::Owned("NonsenseMethod".to_string())).to_string()
);
}
#[cfg(feature = "http")]
#[test]
fn test_http_crate() {
assert_eq!(HttpMethod::GET, http::Method::GET.clone().into());
assert_eq!(
HttpMethod(Cow::Owned("BANANA".to_string())),
http::Method::from_str("BANANA").unwrap().into()
);
assert_eq!(HttpMethod::GET, (&http::Method::GET).into());
let x = http::Method::from_str("BANANA").unwrap();
assert_eq!(
HttpMethod(Cow::Borrowed("BANANA")),
(&x).into()
);
f.write_str(match self {
HttpMethod::GET => "GET",
HttpMethod::POST => "POST",
HttpMethod::HEAD => "HEAD",
HttpMethod::OTHER(s) => s,
})
}
}

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

@ -62,13 +62,9 @@ pub fn parse(www_authorize: &str) -> Result<WwwAuthenticateHeader> {
WwwAuthenticateHeader::parse(www_authorize)
}
#[cfg(test)]
mod test {
use crate::{AuthContext, Error};
#[test]
fn test_parse_respond() {
let src = r#"
#[test]
fn test_parse_respond() {
let src = r#"
Digest
realm="http-auth@example.org",
qop="auth, auth-int",
@ -77,17 +73,17 @@ mod test {
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 "); // This is only for easier reading
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",
@ -99,12 +95,11 @@ 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();
}

Loading…
Cancel
Save