@ -6,6 +6,7 @@ use std::str::FromStr;
use crate ::enums ::{ Algorithm , AlgorithmType , Charset , HttpMethod , Qop , QopAlgo } ;
use crate ::{ Error ::* , Result } ;
use std ::borrow ::Cow ;
/// slash quoting for digest strings
trait QuoteForDigest {
@ -18,12 +19,44 @@ impl QuoteForDigest for &str {
}
}
impl < ' a > QuoteForDigest for Cow < ' a , str > {
fn quote_for_digest ( & self ) -> String {
self . as_ref ( ) . quote_for_digest ( )
}
}
impl QuoteForDigest for String {
fn quote_for_digest ( & self ) -> String {
self . replace ( "\\" , "\\\\" ) . replace ( "\"" , "\\\"" )
}
}
/// Join a Vec of Display items using a separator
fn join_vec < T : ToString > ( vec : & Vec < T > , sep : & str ) -> String {
vec . iter ( )
. map ( ToString ::to_string )
. collect ::< Vec < _ > > ( )
. join ( sep )
}
enum NamedTag < ' a > {
Quoted ( & ' a str , Cow < ' a , str > ) ,
Plain ( & ' a str , Cow < ' a , str > )
}
impl Display for NamedTag < ' _ > {
fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt ::Result {
match self {
NamedTag ::Quoted ( name , content ) = > {
write! ( f , "{}=\"{}\"" , name , content . quote_for_digest ( ) )
}
NamedTag ::Plain ( name , content ) = > {
write! ( f , "{}={}" , name , content )
}
}
}
}
/// Login attempt context
///
/// All fields are borrowed to reduce runtime overhead; this struct should not be stored anywhere,
@ -199,6 +232,48 @@ impl WwwAuthenticateHeader {
}
}
impl Display for WwwAuthenticateHeader {
fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt ::Result {
let mut entries = Vec ::< NamedTag > ::new ( ) ;
f . write_str ( "Digest " ) ? ;
entries . push ( NamedTag ::Quoted ( "realm" , ( & self . realm ) . into ( ) ) ) ;
if let Some ( ref qops ) = self . qop {
entries . push ( NamedTag ::Quoted ( "qop" , join_vec ( qops , ", " ) . into ( ) ) ) ;
}
if let Some ( ref domains ) = self . domain {
entries . push ( NamedTag ::Quoted ( "domain" , join_vec ( domains , " " ) . into ( ) ) ) ;
}
if self . stale {
entries . push ( NamedTag ::Plain ( "stale" , "true" . into ( ) ) ) ;
}
entries . push ( NamedTag ::Plain ( "algorithm" , self . algorithm . to_string ( ) . into ( ) ) ) ;
entries . push ( NamedTag ::Quoted ( "nonce" , ( & self . nonce ) . into ( ) ) ) ;
if let Some ( ref opaque ) = self . opaque {
entries . push ( NamedTag ::Quoted ( "opaque" , ( opaque ) . into ( ) ) ) ;
}
entries . push ( NamedTag ::Plain ( "charset" , self . charset . to_string ( ) . into ( ) ) ) ;
if self . userhash {
entries . push ( NamedTag ::Plain ( "userhash" , "true" . into ( ) ) ) ;
}
for ( i , e ) in entries . iter ( ) . enumerate ( ) {
if i > 0 {
f . write_str ( ", " ) ? ;
}
f . write_str ( & e . to_string ( ) ) ? ;
}
Ok ( ( ) )
}
}
/// Helper func that parses the key-value string received from server
fn parse_header_map ( input : & str ) -> Result < HashMap < String , String > > {
#[ derive(Debug) ]
@ -355,12 +430,7 @@ impl<'a> AuthorizationHeader<'a> {
QopAlgo ::AUTH
} else {
// parser bug - prompt.qop should have been None
return Err ( BadQopOptions (
vec . iter ( )
. map ( ToString ::to_string )
. collect ::< Vec < String > > ( )
. join ( "," ) ,
) ) ;
return Err ( BadQopOptions ( join_vec ( vec , ", " ) ) ) ;
}
}
} ;
@ -483,44 +553,29 @@ impl<'a> Display for AuthorizationHeader<'a> {
f . write_str ( "Digest " ) ? ;
//TODO charset shenanigans with username* (UTF-8 charset)
f . write_fmt ( format_args! (
"username=\"{}\"" ,
self . username . quote_for_digest ( )
) ) ? ;
f . write_fmt ( format_args! (
", realm=\"{}\"" ,
self . prompt . realm . quote_for_digest ( )
) ) ? ;
f . write_fmt ( format_args! (
", nonce=\"{}\"" ,
self . prompt . nonce . quote_for_digest ( )
) ) ? ;
f . write_fmt ( format_args! ( ", uri=\"{}\"" , self . uri ) ) ? ;
write! ( f , "username=\"{uname}\", realm=\"{realm}\", nonce=\"{nonce}\", uri=\"{uri}\"" ,
uname = self . username . quote_for_digest ( ) ,
realm = self . prompt . realm . quote_for_digest ( ) ,
nonce = self . prompt . nonce . quote_for_digest ( ) ,
uri = self . uri ) ? ;
if self . prompt . qop . is_some ( ) & & self . cnonce . is_some ( ) {
f . write_fmt ( format_args! (
", qop={qop}, nc={nc:08x}, cnonce=\"{cnonce}\"" ,
write! ( f , ", qop={qop}, nc={nc:08x}, cnonce=\"{cnonce}\"" ,
qop = self . qop . as_ref ( ) . unwrap ( ) ,
nc = self . nc ,
cnonce = self . cnonce . as_ref ( ) . unwrap ( ) . quote_for_digest ( ) ,
nc = self . nc
) ) ? ;
) ? ;
}
f . write_fmt ( format_args! (
", response=\"{}\"" ,
self . response . quote_for_digest ( )
) ) ? ;
write! ( f , ", response=\"{}\"" , self . response . quote_for_digest ( ) ) ? ;
if let Some ( opaque ) = & self . prompt . opaque {
f . write_fmt ( format_args! ( ", opaque=\"{}\"" , opaque . quote_for_digest ( ) ) ) ? ;
write! ( f , ", opaque=\"{}\"" , opaque . quote_for_digest ( ) ) ? ;
}
// algorithm can be omitted if it is the default value (or in legacy compat mode)
if self . qop . is_some ( ) | | self . prompt . algorithm . algo ! = AlgorithmType ::MD5 {
f . write_fmt ( format_args! ( ", algorithm={}" , self . prompt . algorithm ) ) ? ;
write! ( f , ", algorithm={}" , self . prompt . algorithm ) ? ;
}
if self . prompt . userhash {
@ -545,7 +600,6 @@ mod tests {
#[ test ]
fn test_parse_header_map ( ) {
{
let src = r #"
realm = "api@example.org" ,
qop = "auth" ,
@ -573,18 +627,23 @@ mod tests {
assert_eq! ( map . get ( "userhash" ) . unwrap ( ) , "true" ) ;
}
#[ test ]
fn test_parse_header_map2 ( )
{
let src = r#"realm="api@example.org""# ;
let map = parse_header_map ( src ) . unwrap ( ) ;
assert_eq! ( map . get ( "realm" ) . unwrap ( ) , "api@example.org" ) ;
}
{
#[ test ]
fn test_parse_header_map3 ( ) {
let src = r#"realm=api@example.org"# ;
let map = parse_header_map ( src ) . unwrap ( ) ;
assert_eq! ( map . get ( "realm" ) . unwrap ( ) , "api@example.org" ) ;
}
#[ test ]
fn test_parse_header_map4 ( ) {
{
let src = "" ;
let map = parse_header_map ( src ) . unwrap ( ) ;
@ -594,12 +653,11 @@ mod tests {
#[ test ]
fn test_www_hdr_parse ( ) {
{
// most things are parsed here...
let src = r #"
realm = "api@example.org" ,
qop = "auth" ,
domain = "/my/nice/url /login /logout"
domain = "/my/nice/url /login /logout" ,
algorithm = SHA - 512 - 256 ,
nonce = "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK" ,
opaque = "HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS" ,
@ -630,7 +688,62 @@ mod tests {
)
}
{
#[ test ]
fn test_www_hdr_tostring ( ) {
let mut hdr = WwwAuthenticateHeader {
domain : Some ( vec! [
"/my/nice/url" . to_string ( ) ,
"/login" . to_string ( ) ,
"/logout" . to_string ( ) ,
] ) ,
realm : "api@example.org" . to_string ( ) ,
nonce : "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK" . to_string ( ) ,
opaque : Some ( "HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS" . to_string ( ) ) ,
stale : false ,
algorithm : Algorithm ::new ( AlgorithmType ::SHA2_512_256 , false ) ,
qop : Some ( vec! [ Qop ::AUTH ] ) ,
userhash : true ,
charset : Charset ::UTF8 ,
nc : 0 ,
} ;
assert_eq! (
r#"Digest realm="api@example.org" ,
qop = "auth" ,
domain = "/my/nice/url /login /logout" ,
algorithm = SHA - 512 - 256 ,
nonce = "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK" ,
opaque = "HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS" ,
charset = UTF - 8 ,
userhash = true "#.replace(" , \ n ", " , " ) , hdr . to_string ( ) ) ;
hdr . stale = true ;
hdr . userhash = false ;
hdr . opaque = None ;
hdr . qop = None ;
assert_eq! (
r#"Digest realm="api@example.org" ,
domain = "/my/nice/url /login /logout" ,
stale = true ,
algorithm = SHA - 512 - 256 ,
nonce = "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK" ,
charset = UTF - 8 "#.replace(" , \ n ", " , " ) , hdr . to_string ( ) ) ;
hdr . qop = Some ( vec! [ Qop ::AUTH , Qop ::AUTH_INT ] ) ;
assert_eq! (
r#"Digest realm="api@example.org" ,
qop = "auth, auth-int" ,
domain = "/my/nice/url /login /logout" ,
stale = true ,
algorithm = SHA - 512 - 256 ,
nonce = "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK" ,
charset = UTF - 8 "#.replace(" , \ n ", " , " ) , hdr . to_string ( ) ) ;
}
#[ test ]
fn test_www_hdr_parse2 ( ) {
// verify some defaults
let src = r #"
realm = "a long realm with\\, weird \" characters" ,
@ -658,7 +771,8 @@ mod tests {
)
}
{
#[ test ]
fn test_www_hdr_parse3 ( ) {
// check that it correctly ignores leading Digest
let src = r#"Digest realm="aaa", nonce="bbb""# ;
@ -680,7 +794,6 @@ mod tests {
}
)
}
}
#[ test ]
fn test_rfc2069 ( ) {