restructure to modules

master
Ondřej Hruška 5 years ago
parent c81e3caf17
commit f4c6e4ab3d
Signed by untrusted user: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 1
      Cargo.toml
  2. 365
      src/digest.rs
  3. 182
      src/enums.rs
  4. 31
      src/error.rs
  5. 18
      src/lib.rs
  6. 18
      src/utils.rs

@ -18,4 +18,3 @@ license = "MIT"
rust-crypto = "0.2" rust-crypto = "0.2"
rand = "0.6" rand = "0.6"
hex = "0.3.2" hex = "0.3.2"
failure = "0.1.5"

@ -1,219 +1,29 @@
use crate::utils::QuoteForDigest; use rand::Rng;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::str::FromStr; use std::str::FromStr;
use failure::{Error,Fallible};
use crypto::{
digest::Digest,
md5::Md5,
sha2::Sha256,
sha2::Sha512Trunc256
};
use rand::Rng;
//region Algorithm
/// Algorithm type
#[derive(Debug, PartialEq)]
#[allow(non_camel_case_types)]
pub enum AlgorithmType {
MD5,
SHA2_256,
SHA2_512_256,
}
/// Algorithm and the -sess flag pair
#[derive(Debug, PartialEq)]
pub struct Algorithm {
pub algo: AlgorithmType,
pub sess: bool,
}
impl Algorithm {
/// Compose from algorithm type and the -sess flag
pub fn new(algo: AlgorithmType, sess: bool) -> Algorithm {
Algorithm { algo, sess }
}
/// Calculate a hash of bytes using the selected algorithm
pub fn hash(&self, bytes: &[u8]) -> String {
let mut hash: Box<dyn Digest> = match self.algo {
AlgorithmType::MD5 => Box::new(Md5::new()),
AlgorithmType::SHA2_256 => Box::new(Sha256::new()),
AlgorithmType::SHA2_512_256 => Box::new(Sha512Trunc256::new()),
};
hash.input(bytes);
hash.result_str()
}
/// Calculate a hash of string's bytes using the selected algorithm
pub fn hash_str(&self, bytes: &str) -> String {
self.hash(bytes.as_bytes())
}
}
impl FromStr for Algorithm {
type Err = Error;
/// Parse from the format used in WWW-Authorization
fn from_str(s: &str) -> Fallible<Self> {
match s {
"MD5" => Ok(Algorithm::new(AlgorithmType::MD5, false)),
"MD5-sess" => Ok(Algorithm::new(AlgorithmType::MD5, true)),
"SHA-256" => Ok(Algorithm::new(AlgorithmType::SHA2_256, false)),
"SHA-256-sess" => Ok(Algorithm::new(AlgorithmType::SHA2_256, true)),
"SHA-512-256" => Ok(Algorithm::new(AlgorithmType::SHA2_512_256, false)),
"SHA-512-256-sess" => Ok(Algorithm::new(AlgorithmType::SHA2_512_256, true)),
_ => Err(format_err!("Unknown algorithm: {}", s)),
}
}
}
impl Default for Algorithm {
/// Get a MD5 instance
fn default() -> Self {
Algorithm::new(AlgorithmType::MD5, false)
}
}
impl Display for Algorithm {
/// Format to the form used in HTTP headers
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
f.write_str(match self.algo {
AlgorithmType::MD5 => "MD5",
AlgorithmType::SHA2_256 => "SHA-256",
AlgorithmType::SHA2_512_256 => "SHA-512-256",
})?;
if self.sess {
f.write_str("-sess")?;
}
Ok(())
}
}
//endregion use crate::enums::{Algorithm, AlgorithmType, Charset, HttpMethod, Qop, QopAlgo};
//region Qop use crate::{Error::*, Result};
/// QOP field values /// slash quoting for digest strings
#[derive(Debug, PartialEq)] trait QuoteForDigest {
#[allow(non_camel_case_types)] fn quote_for_digest(&self) -> String;
pub enum Qop {
/// QOP field not set by server
AUTH,
AUTH_INT,
}
impl FromStr for Qop {
type Err = Error;
/// Parse from "auth" or "auth-int" as used in HTTP headers
fn from_str(s: &str) -> Fallible<Self> {
match s {
"auth" => Ok(Qop::AUTH),
"auth-int" => Ok(Qop::AUTH_INT),
_ => Err(format_err!("Unknown QOP value: {}", s)),
}
}
} }
impl Display for Qop { impl QuoteForDigest for &str {
/// Convert to "auth" or "auth-int" as used in HTTP headers fn quote_for_digest(&self) -> String {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { self.to_string().quote_for_digest()
f.write_str(match self {
Qop::AUTH => "auth",
Qop::AUTH_INT => "auth-int",
})?;
Ok(())
} }
} }
#[derive(Debug)] impl QuoteForDigest for String {
#[allow(non_camel_case_types)] fn quote_for_digest(&self) -> String {
enum QopAlgo<'a> { self.replace("\\", "\\\\").replace("\"", "\\\"")
NONE,
AUTH,
AUTH_INT(&'a [u8]),
}
// casting back...
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),
}
} }
} }
//endregion
//region Charset
/// Charset field value as specified by the server
#[derive(Debug, PartialEq)]
pub enum Charset {
ASCII,
UTF8,
}
impl FromStr for Charset {
type Err = Error;
/// Parse from string (only UTF-8 supported, as prescribed by the specification)
fn from_str(s: &str) -> Fallible<Self> {
match s {
"UTF-8" => Ok(Charset::UTF8),
_ => Err(format_err!("Unknown charset value: {}", s)),
}
}
}
//endregion
//region HttpMethod
/// HTTP method (used when generating the response hash for some Qop options)
#[derive(Debug)]
pub enum HttpMethod {
GET,
POST,
HEAD,
OTHER(&'static str),
}
impl Default for HttpMethod {
fn default() -> Self {
HttpMethod::GET
}
}
impl Display for HttpMethod {
/// Convert to uppercase string
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
f.write_str(match self {
HttpMethod::GET => "GET",
HttpMethod::POST => "POST",
HttpMethod::HEAD => "HEAD",
HttpMethod::OTHER(s) => s,
})?;
Ok(())
}
}
//endregion
//region AuthContext
/// Login attempt context /// Login attempt context
/// ///
/// All fields are borrowed to reduce runtime overhead; this struct should not be stored anywhere, /// All fields are borrowed to reduce runtime overhead; this struct should not be stored anywhere,
@ -238,28 +48,28 @@ pub struct AuthContext<'a> {
impl<'a> AuthContext<'a> { impl<'a> AuthContext<'a> {
/// Construct a new context with the GET verb and no payload body. /// Construct a new context with the GET verb and no payload body.
/// See the other constructors if this does not fit your situation. /// See the other constructors if this does not fit your situation.
pub fn new<'n:'a, 'p:'a, 'u:'a>(username : &'n str, password : &'p str, uri : &'u str) -> Self { pub fn new<'n: 'a, 'p: 'a, 'u: 'a>(username: &'n str, password: &'p str, uri: &'u str) -> Self {
Self::new_with_method(username, password, uri, None, HttpMethod::GET) Self::new_with_method(username, password, uri, None, HttpMethod::GET)
} }
/// Construct a new context with the POST verb and a payload body (may be None). /// Construct a new context with the POST verb and a payload body (may be None).
/// See the other constructors if this does not fit your situation. /// See the other constructors if this does not fit your situation.
pub fn new_post<'n:'a, 'p:'a, 'u:'a, 'b:'a>( pub fn new_post<'n: 'a, 'p: 'a, 'u: 'a, 'b: 'a>(
username : &'n str, username: &'n str,
password : &'p str, password: &'p str,
uri : &'u str, uri: &'u str,
body : Option<&'b [u8]> body: Option<&'b [u8]>,
) -> Self { ) -> Self {
Self::new_with_method(username, password, uri, body, HttpMethod::POST) Self::new_with_method(username, password, uri, body, HttpMethod::POST)
} }
/// Construct a new context with arbitrary verb and, optionally, a payload body /// Construct a new context with arbitrary verb and, optionally, a payload body
pub fn new_with_method<'n:'a, 'p:'a, 'u:'a, 'b:'a>( pub fn new_with_method<'n: 'a, 'p: 'a, 'u: 'a, 'b: 'a>(
username : &'n str, username: &'n str,
password : &'p str, password: &'p str,
uri : &'u str, uri: &'u str,
body : Option<&'b [u8]>, body: Option<&'b [u8]>,
method : HttpMethod method: HttpMethod,
) -> Self { ) -> Self {
Self { Self {
username, username,
@ -267,19 +77,16 @@ impl<'a> AuthContext<'a> {
uri, uri,
body, body,
method, method,
cnonce: None cnonce: None,
} }
} }
pub fn set_custom_cnonce<'x:'a>(&mut self, cnonce : &'x str) { /// Set cnonce to the given value
pub fn set_custom_cnonce<'x: 'a>(&mut self, cnonce: &'x str) {
self.cnonce = Some(cnonce); self.cnonce = Some(cnonce);
} }
} }
//endregion
//region WwwAuthenticateHeader
/// WWW-Authenticate header parsed from HTTP header value /// WWW-Authenticate header parsed from HTTP header value
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct WwwAuthenticateHeader { pub struct WwwAuthenticateHeader {
@ -308,10 +115,22 @@ pub struct WwwAuthenticateHeader {
pub nc: u32, pub nc: u32,
} }
impl FromStr for WwwAuthenticateHeader {
type Err = crate::Error;
/// Parse HTTP header
fn from_str(input: &str) -> Result<Self> {
Self::parse(input)
}
}
impl WwwAuthenticateHeader { impl WwwAuthenticateHeader {
/// Generate an [`AuthorizationHeader`](struct.AuthorizationHeader.html) to be sent to the server in a new request. /// Generate an [`AuthorizationHeader`](struct.AuthorizationHeader.html) to be sent to the server in a new request.
/// The [`self.nc`](struct.AuthorizationHeader.html#structfield.nc) field is incremented. /// The [`self.nc`](struct.AuthorizationHeader.html#structfield.nc) field is incremented.
pub fn respond<'re, 'a:'re, 'c:'re>(&'a mut self, secrets : &'c AuthContext) -> Fallible<AuthorizationHeader<'re>> { pub fn respond<'re, 'a: 're, 'c: 're>(
&'a mut self,
secrets: &'c AuthContext,
) -> Result<AuthorizationHeader<'re>> {
AuthorizationHeader::from_prompt(self, secrets) AuthorizationHeader::from_prompt(self, secrets)
} }
@ -319,8 +138,10 @@ impl WwwAuthenticateHeader {
/// ///
/// # Errors /// # Errors
/// If the header is malformed (e.g. missing 'realm', missing a closing quote, unknown algorithm etc.) /// If the header is malformed (e.g. missing 'realm', missing a closing quote, unknown algorithm etc.)
pub fn parse(input: &str) -> Fallible<Self> { pub fn parse(input: &str) -> Result<Self> {
let mut input = input.trim(); let mut input = input.trim();
// Remove leading "Digest"
if input.starts_with("Digest") { if input.starts_with("Digest") {
input = &input["Digest".len()..]; input = &input["Digest".len()..];
} }
@ -343,11 +164,11 @@ impl WwwAuthenticateHeader {
}, },
realm: match kv.remove("realm") { realm: match kv.remove("realm") {
Some(v) => v, Some(v) => v,
None => bail!("realm not given"), None => return Err(MissingRealm(input.into())),
}, },
nonce: match kv.remove("nonce") { nonce: match kv.remove("nonce") {
Some(v) => v, Some(v) => v,
None => bail!("nonce not given"), None => return Err(MissingNonce(input.into())),
}, },
opaque: kv.remove("opaque"), opaque: kv.remove("opaque"),
stale: match kv.get("stale") { stale: match kv.get("stale") {
@ -373,13 +194,13 @@ impl WwwAuthenticateHeader {
Some(v) => &v.to_ascii_lowercase() == "true", Some(v) => &v.to_ascii_lowercase() == "true",
None => false, None => false,
}, },
nc : 0 nc: 0,
}) })
} }
} }
/// Helper func that parses the key-value string received from server /// Helper func that parses the key-value string received from server
pub fn parse_header_map(input: &str) -> Fallible<HashMap<String, String>> { fn parse_header_map(input: &str) -> Result<HashMap<String, String>> {
#[derive(Debug)] #[derive(Debug)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
enum ParserState { enum ParserState {
@ -462,28 +283,17 @@ pub fn parse_header_map(input: &str) -> Fallible<HashMap<String, String>> {
parsed.insert(current_token.unwrap().to_string(), current_value); // consume the value here parsed.insert(current_token.unwrap().to_string(), current_value); // consume the value here
} }
ParserState::P_WHITE => {} ParserState::P_WHITE => {}
_ => bail!("Unexpected end state {:?}", state), _ => return Err(InvalidHeaderSyntax(input.into())),
} }
Ok(parsed) Ok(parsed)
} }
impl FromStr for WwwAuthenticateHeader {
type Err = Error;
/// Parse HTTP header
fn from_str(input: &str) -> Fallible<Self> {
Self::parse(input)
}
}
//endregion
//region AuthorizationHeader
/// Header sent back to the server, including password hashes. /// Header sent back to the server, including password hashes.
/// ///
/// This can be obtained by calling [`AuthorizationHeader::from_prompt()`](#method.from_prompt), or from the [`WwwAuthenticateHeader`](struct.WwwAuthenticateHeader.html) prompt struct with [`.respond()`](struct.WwwAuthenticateHeader.html#method.respond) /// This can be obtained by calling [`AuthorizationHeader::from_prompt()`](#method.from_prompt),
/// or from the [`WwwAuthenticateHeader`](struct.WwwAuthenticateHeader.html) prompt struct
/// with [`.respond()`](struct.WwwAuthenticateHeader.html#method.respond)
#[derive(Debug)] #[derive(Debug)]
pub struct AuthorizationHeader<'ctx> { pub struct AuthorizationHeader<'ctx> {
/// The server header that triggered the authentication flow; used to retrieve some additional /// The server header that triggered the authentication flow; used to retrieve some additional
@ -519,9 +329,10 @@ impl<'a> AuthorizationHeader<'a> {
/// ///
/// Fails if the source header is malformed so much that we can't figure out /// Fails if the source header is malformed so much that we can't figure out
/// a proper response (e.g. given but invalid QOP options) /// a proper response (e.g. given but invalid QOP options)
pub fn from_prompt<'p:'a, 's:'a>( pub fn from_prompt<'p: 'a, 's: 'a>(
prompt: &'p mut WwwAuthenticateHeader, context: &'s AuthContext prompt: &'p mut WwwAuthenticateHeader,
) -> Fallible<AuthorizationHeader<'a>> { context: &'s AuthContext,
) -> Result<AuthorizationHeader<'a>> {
// figure out which QOP option to use // figure out which QOP option to use
let empty_vec = vec![]; let empty_vec = vec![];
let qop_algo = match &prompt.qop { let qop_algo = match &prompt.qop {
@ -544,7 +355,12 @@ impl<'a> AuthorizationHeader<'a> {
QopAlgo::AUTH QopAlgo::AUTH
} else { } else {
// parser bug - prompt.qop should have been None // parser bug - prompt.qop should have been None
bail!("Bad QOP options - {:#?}", vec); return Err(BadQopOptions(
vec.iter()
.map(ToString::to_string)
.collect::<Vec<String>>()
.join(","),
));
} }
} }
}; };
@ -604,13 +420,14 @@ impl<'a> AuthorizationHeader<'a> {
"{username}:{realm}", "{username}:{realm}",
username = context.username, username = context.username,
realm = prompt.realm realm = prompt.realm
).as_bytes() )
.as_bytes(),
) )
} else { } else {
context.username.to_owned() context.username.to_owned()
}; };
let qop : Option<Qop> = qop_algo.into(); let qop: Option<Qop> = qop_algo.into();
let ha1 = h.hash_str(&a1); let ha1 = h.hash_str(&a1);
let ha2 = h.hash_str(&a2); let ha2 = h.hash_str(&a2);
@ -662,7 +479,7 @@ impl<'a> AuthorizationHeader<'a> {
} }
impl<'a> Display for AuthorizationHeader<'a> { impl<'a> Display for AuthorizationHeader<'a> {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("Digest ")?; f.write_str("Digest ")?;
//TODO charset shenanigans with username* (UTF-8 charset) //TODO charset shenanigans with username* (UTF-8 charset)
@ -714,21 +531,17 @@ impl<'a> Display for AuthorizationHeader<'a> {
} }
} }
//endregion
//region TESTS
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str::FromStr; use super::parse_header_map;
use super::WwwAuthenticateHeader;
use super::AuthorizationHeader;
use super::Algorithm; use super::Algorithm;
use super::AlgorithmType;
use super::AuthorizationHeader;
use super::Charset; use super::Charset;
use super::Qop; use super::Qop;
use super::AlgorithmType; use super::WwwAuthenticateHeader;
use super::parse_header_map;
use crate::digest::AuthContext; use crate::digest::AuthContext;
use std::str::FromStr;
#[test] #[test]
fn test_parse_header_map() { fn test_parse_header_map() {
@ -812,7 +625,7 @@ mod tests {
qop: Some(vec![Qop::AUTH]), qop: Some(vec![Qop::AUTH]),
userhash: true, userhash: true,
charset: Charset::UTF8, charset: Charset::UTF8,
nc: 0 nc: 0,
} }
) )
} }
@ -840,7 +653,7 @@ mod tests {
qop: Some(vec![Qop::AUTH_INT]), qop: Some(vec![Qop::AUTH_INT]),
userhash: false, userhash: false,
charset: Charset::ASCII, charset: Charset::ASCII,
nc: 0 nc: 0,
} }
) )
} }
@ -863,7 +676,7 @@ mod tests {
qop: None, qop: None,
userhash: false, userhash: false,
charset: Charset::ASCII, charset: Charset::ASCII,
nc: 0 nc: 0,
} }
) )
} }
@ -895,7 +708,7 @@ Digest username="Mufasa",
response="1949323746fe6a43ef61f9606e7febea", response="1949323746fe6a43ef61f9606e7febea",
opaque="5ccc069c403ebaf9f0171e9517f40e41" opaque="5ccc069c403ebaf9f0171e9517f40e41"
"# "#
.trim() .trim()
); );
} }
@ -932,7 +745,7 @@ Digest username="Mufasa",
opaque="5ccc069c403ebaf9f0171e9517f40e41", opaque="5ccc069c403ebaf9f0171e9517f40e41",
algorithm=MD5 algorithm=MD5
"# "#
.trim() .trim()
); );
} }
@ -969,7 +782,7 @@ Digest username="Mufasa",
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
algorithm=MD5 algorithm=MD5
"# "#
.trim() .trim()
); );
} }
@ -986,16 +799,16 @@ Digest username="Mufasa",
let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html"); let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"); context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
// //
// let secrets = AuthSecrets { // let secrets = AuthSecrets {
// username: "Mufasa".to_string(), // username: "Mufasa".to_string(),
// password: "Circle of Life".to_string(), // password: "Circle of Life".to_string(),
// uri: "/dir/index.html".to_string(), // uri: "/dir/index.html".to_string(),
// body: None, // body: None,
// method: HttpMethod::GET, // method: HttpMethod::GET,
// nc: 1, // nc: 1,
// cnonce: Some("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ".to_string()), // cnonce: Some("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ".to_string()),
// }; // };
let mut prompt = WwwAuthenticateHeader::from_str(src).unwrap(); let mut prompt = WwwAuthenticateHeader::from_str(src).unwrap();
let answer = AuthorizationHeader::from_prompt(&mut prompt, &context).unwrap(); let answer = AuthorizationHeader::from_prompt(&mut prompt, &context).unwrap();
@ -1017,9 +830,7 @@ Digest username="Mufasa",
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
algorithm=SHA-256 algorithm=SHA-256
"# "#
.trim() .trim()
); );
} }
} }
//endregion

@ -0,0 +1,182 @@
use crate::{Error, Error::*, Result};
use crypto::{digest::Digest, md5::Md5, sha2::Sha256, sha2::Sha512Trunc256};
use std::fmt;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
/// Algorithm type
#[derive(Debug, PartialEq)]
#[allow(non_camel_case_types)]
pub enum AlgorithmType {
MD5,
SHA2_256,
SHA2_512_256,
}
/// Algorithm and the -sess flag pair
#[derive(Debug, PartialEq)]
pub struct Algorithm {
pub algo: AlgorithmType,
pub sess: bool,
}
impl Algorithm {
/// Compose from algorithm type and the -sess flag
pub fn new(algo: AlgorithmType, sess: bool) -> Algorithm {
Algorithm { algo, sess }
}
/// Calculate a hash of bytes using the selected algorithm
pub fn hash(&self, bytes: &[u8]) -> String {
let mut hash: Box<dyn Digest> = match self.algo {
AlgorithmType::MD5 => Box::new(Md5::new()),
AlgorithmType::SHA2_256 => Box::new(Sha256::new()),
AlgorithmType::SHA2_512_256 => Box::new(Sha512Trunc256::new()),
};
hash.input(bytes);
hash.result_str()
}
/// Calculate a hash of string's bytes using the selected algorithm
pub fn hash_str(&self, bytes: &str) -> String {
self.hash(bytes.as_bytes())
}
}
impl FromStr for Algorithm {
type Err = Error;
/// Parse from the format used in WWW-Authorization
fn from_str(s: &str) -> Result<Self> {
match s {
"MD5" => Ok(Algorithm::new(AlgorithmType::MD5, false)),
"MD5-sess" => Ok(Algorithm::new(AlgorithmType::MD5, true)),
"SHA-256" => Ok(Algorithm::new(AlgorithmType::SHA2_256, false)),
"SHA-256-sess" => Ok(Algorithm::new(AlgorithmType::SHA2_256, true)),
"SHA-512-256" => Ok(Algorithm::new(AlgorithmType::SHA2_512_256, false)),
"SHA-512-256-sess" => Ok(Algorithm::new(AlgorithmType::SHA2_512_256, true)),
_ => Err(UnknownAlgorithm(s.into())),
}
}
}
impl Default for Algorithm {
/// Get a MD5 instance
fn default() -> Self {
Algorithm::new(AlgorithmType::MD5, false)
}
}
impl Display for Algorithm {
/// Format to the form used in HTTP headers
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(match self.algo {
AlgorithmType::MD5 => "MD5",
AlgorithmType::SHA2_256 => "SHA-256",
AlgorithmType::SHA2_512_256 => "SHA-512-256",
})?;
if self.sess {
f.write_str("-sess")?;
}
Ok(())
}
}
/// QOP field values
#[derive(Debug, PartialEq)]
#[allow(non_camel_case_types)]
pub enum Qop {
AUTH,
AUTH_INT,
}
impl FromStr for Qop {
type Err = Error;
/// Parse from "auth" or "auth-int" as used in HTTP headers
fn from_str(s: &str) -> Result<Self> {
match s {
"auth" => Ok(Qop::AUTH),
"auth-int" => Ok(Qop::AUTH_INT),
_ => Err(BadQop(s.into())),
}
}
}
impl Display for Qop {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Qop::AUTH => "auth",
Qop::AUTH_INT => "auth-int",
})
}
}
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum QopAlgo<'a> {
NONE,
AUTH,
AUTH_INT(&'a [u8]),
}
// casting back...
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),
}
}
}
/// Charset field value as specified by the server
#[derive(Debug, PartialEq)]
pub enum Charset {
ASCII,
UTF8,
}
impl FromStr for Charset {
type Err = Error;
/// Parse from string (only UTF-8 supported, as prescribed by the specification)
fn from_str(s: &str) -> Result<Self> {
match s {
"UTF-8" => Ok(Charset::UTF8),
_ => Err(BadCharset(s.into())),
}
}
}
/// HTTP method (used when generating the response hash for some Qop options)
#[derive(Debug)]
pub enum HttpMethod {
GET,
POST,
HEAD,
OTHER(&'static str),
}
impl Default for HttpMethod {
fn default() -> Self {
HttpMethod::GET
}
}
impl Display for HttpMethod {
/// Convert to uppercase string
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(match self {
HttpMethod::GET => "GET",
HttpMethod::POST => "POST",
HttpMethod::HEAD => "HEAD",
HttpMethod::OTHER(s) => s,
})
}
}

@ -0,0 +1,31 @@
use std::fmt::{self, Display, Formatter};
use std::result;
#[derive(Debug)]
pub enum Error {
BadCharset(String),
UnknownAlgorithm(String),
BadQop(String),
MissingRealm(String),
MissingNonce(String),
InvalidHeaderSyntax(String),
BadQopOptions(String),
}
pub type Result<T> = result::Result<T, Error>;
use Error::*;
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
BadCharset(ctx) => write!(f, "Bad charset: {}", ctx),
UnknownAlgorithm(ctx) => write!(f, "Unknown algorithm: {}", ctx),
BadQop(ctx) => write!(f, "Bad Qop option: {}", ctx),
MissingRealm(ctx) => write!(f, "Missing 'realm' in WWW-Authenticate: {}", ctx),
MissingNonce(ctx) => write!(f, "Missing 'nonce' in WWW-Authenticate: {}", ctx),
InvalidHeaderSyntax(ctx) => write!(f, "Invalid header syntax: {}", ctx),
BadQopOptions(ctx) => write!(f, "Illegal Qop in prompt: {}", ctx),
}
}
}

@ -42,19 +42,19 @@
//! assert_eq!(answer2, r#"Digest username="Mufasa", realm="http-auth@example.org", nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", uri="/dir/index.html", qop=auth, nc=00000002, cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", response="4b5d595ecf2db9df612ea5b45cd97101", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", algorithm=MD5"#); //! assert_eq!(answer2, r#"Digest username="Mufasa", realm="http-auth@example.org", nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", uri="/dir/index.html", qop=auth, nc=00000002, cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", response="4b5d595ecf2db9df612ea5b45cd97101", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", algorithm=MD5"#);
//! ``` //! ```
#[macro_use] extern crate failure;
use failure::Fallible;
mod digest; mod digest;
mod utils; mod enums;
mod error;
pub use error::{Error, Result};
pub use crate::digest::{AuthContext, AuthorizationHeader, WwwAuthenticateHeader};
pub use crate::digest::{ pub use crate::enums::*;
Algorithm, AuthContext, AuthorizationHeader, HttpMethod, Qop, WwwAuthenticateHeader,
};
/// Parse the WWW-Authorization header value. /// Parse the WWW-Authorization header value.
/// It's just a convenience method to call [`WwwAuthenticateHeader::parse()`](struct.WwwAuthenticateHeader.html#method.parse). /// It's just a convenience method to call [`WwwAuthenticateHeader::parse()`](struct.WwwAuthenticateHeader.html#method.parse).
pub fn parse(www_authorize : &str) -> Fallible<WwwAuthenticateHeader> { pub fn parse(www_authorize: &str) -> Result<WwwAuthenticateHeader> {
WwwAuthenticateHeader::parse(www_authorize) WwwAuthenticateHeader::parse(www_authorize)
} }
@ -91,6 +91,6 @@ Digest username="Mufasa",
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
algorithm=MD5 algorithm=MD5
"# "#
.trim() .trim()
); );
} }

@ -1,18 +0,0 @@
use std::string::ToString;
/// slash quoting for digest strings
pub trait QuoteForDigest {
fn quote_for_digest(&self) -> String;
}
impl QuoteForDigest for &str {
fn quote_for_digest(&self) -> String {
self.to_string().quote_for_digest()
}
}
impl QuoteForDigest for String {
fn quote_for_digest(&self) -> String {
self.replace("\\", "\\\\").replace("\"", "\\\"")
}
}
Loading…
Cancel
Save