www_authenticate/
lib.rs

1//! www-authenticate
2//! missing HTTP WWW-Authenticate header parser/printer for hyper
3
4extern crate hyperx;
5extern crate unicase;
6extern crate url;
7
8use hyperx::header::{Formatter, Header, RawLike};
9use std::borrow::Cow;
10use std::collections::HashMap;
11use std::fmt;
12use std::mem;
13use std::ops::{Deref, DerefMut};
14use unicase::UniCase;
15
16/// `WWW-Authenticate` header, defined in
17/// [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.1)
18///
19/// The `WWW-Authenticate` header field indicates the authentication
20/// scheme(s) and parameters applicable to the target resource.
21/// This MUST be contained in HTTP responses whose status is 401.
22///
23/// # ABNF
24/// ```plain
25/// WWW-Authenticate = 1#challenge
26/// ```
27///
28/// # Example values
29/// * `Basic realm="foo", charset="UTF-8"`
30/// * `Digest
31///    realm="http-auth@example.org",
32///    qop="auth, auth-int",
33///    algorithm=MD5,
34///    nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
35///    opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"`
36///
37/// # Examples
38///
39/// ```
40/// # extern crate hyperx;
41/// # extern crate www_authenticate;
42/// # use hyperx::header::Headers;
43/// # use www_authenticate::{WwwAuthenticate, DigestChallenge, Qop,
44/// # Algorithm};
45/// # fn main(){
46/// let auth = WwwAuthenticate::new(
47///     DigestChallenge {
48///         realm: Some("http-auth@example.org".into()),
49///         qop: Some(vec![Qop::Auth, Qop::AuthInt]),
50///         algorithm: Some(Algorithm::Sha256),
51///         nonce: Some("7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v".into()),
52///         opaque: Some("FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
53///                          .into()),
54///         domain: None,
55///         stale: None,
56///         userhash: None,
57/// });
58/// let mut headers = Headers::new();
59/// headers.set(auth);
60/// # }
61/// ```
62///
63/// ```
64/// # extern crate hyperx;
65/// # extern crate www_authenticate;
66/// # use hyperx::header::Headers;
67/// # use www_authenticate::{WwwAuthenticate, BasicChallenge};
68/// # fn main(){
69/// let auth = WwwAuthenticate::new(BasicChallenge{realm: "foo".into()});
70/// let mut headers = Headers::new();
71/// headers.set(auth);
72/// let auth = headers.get::<WwwAuthenticate>().unwrap();
73/// let basics = auth.get::<BasicChallenge>().unwrap();
74/// # }
75/// ```
76#[derive(Debug, Clone)]
77pub struct WwwAuthenticate(HashMap<UniCase<CowStr>, Vec<RawChallenge>>);
78
79/// The challenge described in
80/// [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.1).
81/// Used in `WWW-Authenticate` header.
82pub trait Challenge: Clone {
83    fn challenge_name() -> &'static str;
84    fn from_raw(raw: RawChallenge) -> Option<Self>;
85    fn into_raw(self) -> RawChallenge;
86}
87
88impl WwwAuthenticate {
89    pub fn new<C: Challenge>(c: C) -> Self {
90        let mut auth = WwwAuthenticate(HashMap::new());
91        auth.set(c);
92        auth
93    }
94
95    pub fn new_from_raw(scheme: String, raw: RawChallenge) -> Self {
96        let mut auth = WwwAuthenticate(HashMap::new());
97        auth.set_raw(scheme, raw);
98        auth
99    }
100
101    /// find challenges and convert them into `C` if found.
102    pub fn get<C: Challenge>(&self) -> Option<Vec<C>> {
103        self.0
104            .get(&UniCase(CowStr(Cow::Borrowed(C::challenge_name()))))
105            .map(|m| m.iter().map(Clone::clone).flat_map(C::from_raw).collect())
106    }
107
108    /// find challenges and return it if found
109    pub fn get_raw(&self, name: &str) -> Option<&[RawChallenge]> {
110        self.0
111            .get(&UniCase(CowStr(Cow::Borrowed(unsafe {
112                mem::transmute::<&str, &'static str>(name)
113            }))))
114            .map(AsRef::as_ref)
115    }
116
117    /// set a challenge. This replaces existing challenges of the same name.
118    pub fn set<C: Challenge>(&mut self, c: C) -> bool {
119        self.0
120            .insert(
121                UniCase(CowStr(Cow::Borrowed(C::challenge_name()))),
122                vec![c.into_raw()],
123            )
124            .is_some()
125    }
126
127    /// set a challenge. This replaces existing challenges of the same name.
128    pub fn set_raw(&mut self, scheme: String, raw: RawChallenge) -> bool {
129        self.0
130            .insert(UniCase(CowStr(Cow::Owned(scheme))), vec![raw])
131            .is_some()
132    }
133
134    /// append a challenge. This appends existing challenges of the same name.
135    pub fn append<C: Challenge>(&mut self, c: C) {
136        self.0
137            .entry(UniCase(CowStr(Cow::Borrowed(C::challenge_name()))))
138            .or_insert_with(Vec::new)
139            .push(c.into_raw())
140    }
141
142    /// append a challenge. This appends existing challenges of the same name.
143    pub fn append_raw(&mut self, scheme: String, raw: RawChallenge) {
144        self.0
145            .entry(UniCase(CowStr(Cow::Owned(scheme))))
146            .or_insert_with(Vec::new)
147            .push(raw)
148    }
149
150    /// test if the challenge exists
151    pub fn has<C: Challenge>(&self) -> bool {
152        self.get::<C>().is_some()
153    }
154}
155
156impl fmt::Display for WwwAuthenticate {
157    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158        for (scheme, values) in &self.0 {
159            for value in values.iter() {
160                // tail commas are allowed
161                write!(f, "{} {}, ", scheme, value)?;
162            }
163        }
164        Ok(())
165    }
166}
167
168impl Header for WwwAuthenticate {
169    fn header_name() -> &'static str {
170        "WWW-Authenticate"
171    }
172
173    fn parse_header<'a, T>(raw: &'a T) -> hyperx::Result<Self>
174    where
175        T: RawLike<'a>,
176    {
177        let mut map = HashMap::new();
178        for data in raw.iter() {
179            let stream = parser::Stream::new(data);
180            loop {
181                let (scheme, challenge) = match stream.challenge() {
182                    Ok(v) => v,
183                    Err(e) => {
184                        if stream.is_end() {
185                            break;
186                        } else {
187                            return Err(e);
188                        }
189                    }
190                };
191                // TODO: treat the cases when a scheme is duplicated
192                map.entry(UniCase(CowStr(Cow::Owned(scheme))))
193                    .or_insert_with(Vec::new)
194                    .push(challenge);
195            }
196        }
197        Ok(WwwAuthenticate(map))
198    }
199
200    fn fmt_header(&self, f: &mut Formatter) -> fmt::Result {
201        f.fmt_line(self)
202    }
203}
204
205macro_rules! try_opt {
206    ($e:expr) => {
207        match $e {
208            Some(e) => e,
209            None => return None,
210        }
211    };
212}
213
214#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
215struct CowStr(Cow<'static, str>);
216
217impl Deref for CowStr {
218    type Target = Cow<'static, str>;
219
220    fn deref(&self) -> &Cow<'static, str> {
221        &self.0
222    }
223}
224
225impl fmt::Debug for CowStr {
226    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
227        fmt::Debug::fmt(&self.0, f)
228    }
229}
230
231impl fmt::Display for CowStr {
232    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233        fmt::Display::fmt(&self.0, f)
234    }
235}
236
237impl DerefMut for CowStr {
238    fn deref_mut(&mut self) -> &mut Cow<'static, str> {
239        &mut self.0
240    }
241}
242
243impl AsRef<str> for CowStr {
244    fn as_ref(&self) -> &str {
245        self
246    }
247}
248
249pub use self::raw::*;
250mod raw {
251    use super::*;
252    use std::borrow::Cow;
253    use std::mem;
254    use unicase::UniCase;
255
256    #[derive(Debug, Clone, PartialEq, Eq)]
257    enum Quote {
258        Always,
259        IfNeed,
260    }
261
262    /// A representation of the challenge fields
263    #[derive(Debug, Clone, PartialEq, Eq)]
264    pub struct ChallengeFields(HashMap<UniCase<CowStr>, (String, Quote)>);
265
266    impl Default for ChallengeFields {
267        fn default() -> Self {
268            Self::new()
269        }
270    }
271
272    impl ChallengeFields {
273        pub fn new() -> Self {
274            ChallengeFields(HashMap::new())
275        }
276        // fn values(&self) -> Values<K, V>
277        // fn values_mut(&mut self) -> ValuesMut<K, V>
278        // fn iter(&self) -> Iter<K, V>
279        // fn iter_mut(&mut self) -> IterMut<K, V>
280        // fn entry(&mut self, key: K) -> Entry<K, V>
281        pub fn len(&self) -> usize {
282            self.0.len()
283        }
284        pub fn is_empty(&self) -> bool {
285            self.0.is_empty()
286        }
287        // fn drain(&mut self) -> Drain<K, V>
288        pub fn clear(&mut self) {
289            self.0.clear()
290        }
291        pub fn get(&self, k: &str) -> Option<&String> {
292            self.0
293                .get(&UniCase(CowStr(Cow::Borrowed(unsafe {
294                    mem::transmute::<&str, &'static str>(k)
295                }))))
296                .map(|&(ref s, _)| s)
297        }
298        pub fn contains_key(&self, k: &str) -> bool {
299            self.0.contains_key(&UniCase(CowStr(Cow::Borrowed(unsafe {
300                mem::transmute::<&str, &'static str>(k)
301            }))))
302        }
303        pub fn get_mut(&mut self, k: &str) -> Option<&mut String> {
304            self.0
305                .get_mut(&UniCase(CowStr(Cow::Borrowed(unsafe {
306                    mem::transmute::<&str, &'static str>(k)
307                }))))
308                .map(|&mut (ref mut s, _)| s)
309        }
310        pub fn insert(&mut self, k: String, v: String) -> Option<String> {
311            self.0
312                .insert(UniCase(CowStr(Cow::Owned(k))), (v, Quote::IfNeed))
313                .map(|(s, _)| s)
314        }
315        pub fn insert_quoting(&mut self, k: String, v: String) -> Option<String> {
316            self.0
317                .insert(UniCase(CowStr(Cow::Owned(k))), (v, Quote::Always))
318                .map(|(s, _)| s)
319        }
320        pub fn insert_static(&mut self, k: &'static str, v: String) -> Option<String> {
321            self.0
322                .insert(UniCase(CowStr(Cow::Borrowed(k))), (v, Quote::IfNeed))
323                .map(|(s, _)| s)
324        }
325        pub fn insert_static_quoting(&mut self, k: &'static str, v: String) -> Option<String> {
326            self.0
327                .insert(UniCase(CowStr(Cow::Borrowed(k))), (v, Quote::Always))
328                .map(|(s, _)| s)
329        }
330        pub fn remove(&mut self, k: &str) -> Option<String> {
331            self.0
332                .remove(&UniCase(CowStr(Cow::Borrowed(unsafe {
333                    mem::transmute::<&str, &'static str>(k)
334                }))))
335                .map(|(s, _)| s)
336        }
337    }
338    // index
339
340    /// A representation of raw challenges. A Challenge is either a token or
341    /// fields.
342    #[derive(Debug, Clone, PartialEq, Eq)]
343    pub enum RawChallenge {
344        Token68(String),
345        Fields(ChallengeFields),
346    }
347
348    fn need_quote(s: &str, q: &Quote) -> bool {
349        if q == &Quote::Always {
350            true
351        } else {
352            s.bytes().any(|c| !parser::is_token_char(c))
353        }
354    }
355
356    impl fmt::Display for RawChallenge {
357        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
358            use self::RawChallenge::*;
359            match *self {
360                Token68(ref token) => write!(f, "{}", token)?,
361                Fields(ref fields) => {
362                    for (k, &(ref v, ref quote)) in fields.0.iter() {
363                        if need_quote(v, quote) {
364                            write!(f, "{}={:?}, ", k, v)?
365                        } else {
366                            write!(f, "{}={}, ", k, v)?
367                        }
368                    }
369                }
370            }
371            Ok(())
372        }
373    }
374}
375
376pub use self::basic::*;
377mod basic {
378    use super::raw::RawChallenge;
379    use super::*;
380
381    /// The challenge for Basic authentication
382    #[derive(Debug, Clone, Eq, PartialEq, Hash)]
383    pub struct BasicChallenge {
384        /// realm of the authentication
385        pub realm: String,
386        // pub charset: Option<Charset>
387    }
388
389    impl Challenge for BasicChallenge {
390        fn challenge_name() -> &'static str {
391            "Basic"
392        }
393        fn from_raw(raw: RawChallenge) -> Option<Self> {
394            use self::RawChallenge::*;
395            match raw {
396                Token68(_) => None,
397                Fields(mut map) => {
398                    let realm = try_opt!(map.remove("realm"));
399                    // only "UTF-8" is allowed.
400                    // See https://tools.ietf.org/html/rfc7617#section-2.1
401                    if let Some(c) = map.remove("charset") {
402                        if UniCase(&c) == UniCase("UTF-8") {
403                        } else {
404                            return None;
405                        }
406                    }
407
408                    if !map.is_empty() {
409                        return None;
410                    }
411                    Some(BasicChallenge { realm })
412                }
413            }
414        }
415        fn into_raw(self) -> RawChallenge {
416            let mut map = ChallengeFields::new();
417            map.insert_static("realm", self.realm);
418            RawChallenge::Fields(map)
419        }
420    }
421
422    #[cfg(test)]
423    mod tests {
424        use super::*;
425        use hyperx::header::Raw;
426
427        #[test]
428        fn test_parse_basic() {
429            let input = "Basic realm=\"secret zone\"";
430            let auth = WwwAuthenticate::parse_header(&Raw::from(input)).unwrap();
431            let mut basics = auth.get::<BasicChallenge>().unwrap();
432            assert_eq!(basics.len(), 1);
433            let basic = basics.swap_remove(0);
434            assert_eq!(basic.realm, "secret zone")
435        }
436
437        #[test]
438        fn test_roundtrip_basic() {
439            let basic = BasicChallenge {
440                realm: "secret zone".into(),
441            };
442            let auth = WwwAuthenticate::new(basic.clone());
443            let data = format!("{}", auth);
444            let auth = WwwAuthenticate::parse_header(&Raw::from(data)).unwrap();
445            let basic_tripped = auth.get::<BasicChallenge>().unwrap().swap_remove(0);
446            assert_eq!(basic, basic_tripped);
447        }
448    }
449}
450
451pub use self::digest::*;
452mod digest {
453    use super::*;
454    use std::str::FromStr;
455    use url::Url;
456
457    /// The challenge for Digest authentication
458    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
459    pub struct DigestChallenge {
460        /// realm of the authentication
461        pub realm: Option<String>,
462        /// domains of the authentication
463        pub domain: Option<Vec<Url>>,
464        /// the nonce used in authentiaction
465        pub nonce: Option<String>,
466        /// a string data specified by the server
467        pub opaque: Option<String>,
468        /// a flag indicating that the previous request from
469        /// the client was rejected because the nonce value was stale.
470        pub stale: Option<bool>,
471        /// the algorithm used to produce the digest and unkeyed digest.
472        /// if not present, it is assumed to be Md5
473        pub algorithm: Option<Algorithm>,
474        /// "quality of protection" values supported by the server
475        pub qop: Option<Vec<Qop>>,
476        // pub charset: Option<Charset>,
477        /// this is an OPTIONAL parameter that is used by the server to
478        /// indicate that it supports username hashing.
479        /// default is false if not present
480        pub userhash: Option<bool>,
481    }
482
483    /// Algorithms used to produce the digest and unkeyed digest.
484    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
485    pub enum Algorithm {
486        /// MD5
487        Md5,
488        /// MD5-sess
489        Md5Sess,
490        /// SHA-512-256
491        Sha512Trunc256,
492        /// SHA-512-256-sess
493        Sha512Trunc256Sess,
494        /// SHA-256
495        Sha256,
496        /// SHA-256-sess
497        Sha256Sess,
498        /// other algorithm
499        Other(String),
500    }
501
502    /// Quority of protection
503    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
504    pub enum Qop {
505        /// authentication
506        Auth,
507        /// authentication with integrity protection
508        AuthInt,
509    }
510
511    impl Challenge for DigestChallenge {
512        fn challenge_name() -> &'static str {
513            "Digest"
514        }
515        fn from_raw(raw: RawChallenge) -> Option<Self> {
516            use self::RawChallenge::*;
517            match raw {
518                Token68(_) => None,
519                Fields(mut map) => {
520                    let realm = map.remove("realm");
521                    let domains = map.remove("domain");
522                    let nonce = map.remove("nonce");
523                    let opaque = map.remove("opaque");
524                    let stale = map.remove("stale");
525                    let algorithm = map.remove("algorithm");
526                    let qop = map.remove("qop");
527                    let charset = map.remove("charset");
528                    let userhash = map.remove("userhash");
529
530                    if !map.is_empty() {
531                        return None;
532                    }
533
534                    let domains = domains.and_then(|ds| {
535                        ds.split_whitespace()
536                            .map(Url::from_str)
537                            .map(::std::result::Result::ok)
538                            .collect::<Option<Vec<Url>>>()
539                    });
540                    let stale = stale.map(|s| s == "true");
541                    let algorithm = algorithm.map(|a| {
542                        use self::Algorithm::*;
543                        match a.as_str() {
544                            "MD5" => return Md5,
545                            "MD5-sess" => return Md5Sess,
546                            "SHA-512-256" => return Sha512Trunc256,
547                            "SHA-512-256-sess" => return Sha512Trunc256Sess,
548                            "SHA-256" => return Sha256,
549                            "SHA-256-sess" => return Sha256Sess,
550                            _ => (),
551                        };
552                        Other(a)
553                    });
554                    let qop = match qop {
555                        None => None,
556                        Some(qop) => {
557                            let mut v = vec![];
558                            let s = parser::Stream::new(qop.as_bytes());
559                            loop {
560                                match try_opt!(s.token().ok()) {
561                                    "auth" => v.push(Qop::Auth),
562                                    "auth-int" => v.push(Qop::AuthInt),
563                                    _ => (),
564                                }
565                                try_opt!(s.skip_field_sep().ok());
566                                if s.is_end() {
567                                    break;
568                                }
569                            }
570                            Some(v)
571                        }
572                    };
573                    if let Some(c) = charset {
574                        if UniCase(&c) == UniCase("UTF-8") {
575                        } else {
576                            return None;
577                        }
578                    }
579
580                    let userhash = userhash.and_then(|u| match u.as_str() {
581                        "true" => Some(true),
582                        "false" => Some(false),
583                        _ => None,
584                    });
585                    Some(DigestChallenge {
586                        realm,
587                        domain: domains,
588                        nonce,
589                        opaque,
590                        stale,
591                        algorithm,
592                        qop,
593                        // pub charset: Option<Charset>,
594                        userhash,
595                    })
596                }
597            }
598        }
599        fn into_raw(self) -> RawChallenge {
600            let mut map = ChallengeFields::new();
601            // Notes on quoting/non-quoting from the spec
602            // ttps://tools.ietf.org/html/rfc7616#section-3.3
603            //
604            // > For historical reasons, a sender MUST only generate the quoted string
605            // > syntax values for the following parameters: realm, domain, nonce,
606            // > opaque, and qop.
607            // >
608            // > For historical reasons, a sender MUST NOT generate the quoted string
609            // > syntax values for the following parameters: stale and algorithm.
610
611            if let Some(realm) = self.realm {
612                map.insert_static_quoting("realm", realm);
613            }
614
615            if let Some(domain) = self.domain {
616                let mut d = String::new();
617                d.extend(domain.into_iter().map(Url::into_string).map(|s| s + " "));
618                let len = d.len();
619                d.truncate(len - 1);
620                map.insert_static_quoting("domain", d);
621            }
622            if let Some(nonce) = self.nonce {
623                map.insert_static_quoting("nonce", nonce);
624            }
625            if let Some(opaque) = self.opaque {
626                map.insert_static_quoting("opaque", opaque);
627            }
628            if let Some(stale) = self.stale {
629                map.insert_static("stale", format!("{}", stale));
630            }
631            if let Some(algorithm) = self.algorithm {
632                map.insert_static("algorithm", format!("{}", algorithm));
633            }
634            if let Some(qop) = self.qop {
635                let mut q = String::new();
636                q.extend(qop.into_iter().map(|q| format!("{}", q)).map(|s| s + ", "));
637                let len = q.len();
638                q.truncate(len - 2);
639                map.insert_static_quoting("qop", q);
640            }
641            if let Some(userhash) = self.userhash {
642                map.insert_static("userhash", format!("{}", userhash));
643            }
644            RawChallenge::Fields(map)
645        }
646    }
647
648    impl fmt::Display for Algorithm {
649        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
650            use self::Algorithm::*;
651            match *self {
652                Md5 => write!(f, "MD5"),
653                Md5Sess => write!(f, "MD5-sess"),
654                Sha512Trunc256 => write!(f, "SHA-512-256"),
655                Sha512Trunc256Sess => write!(f, "SHA-512-256-sess"),
656                Sha256 => write!(f, "SHA-256"),
657                Sha256Sess => write!(f, "SHA-256-sess"),
658                Other(ref s) => write!(f, "{}", s),
659            }
660        }
661    }
662
663    impl fmt::Display for Qop {
664        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
665            use self::Qop::*;
666            match *self {
667                Auth => write!(f, "auth"),
668                AuthInt => write!(f, "auth-int"),
669            }
670        }
671    }
672
673    #[cfg(test)]
674    mod tests {
675        use super::*;
676        use hyperx::header::Raw;
677
678        #[test]
679        fn test_parse_digest() {
680            let input = r#"Digest realm="http-auth@example.org", qop="auth, auth-int", algorithm=SHA-256, nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS""#;
681            let auth = WwwAuthenticate::parse_header(&Raw::from(input)).unwrap();
682            let mut digests = auth.get::<DigestChallenge>().unwrap();
683            assert_eq!(digests.len(), 1);
684            let digest = digests.swap_remove(0);
685            assert_eq!(digest.realm, Some("http-auth@example.org".into()));
686            assert_eq!(digest.domain, None);
687            assert_eq!(
688                digest.nonce,
689                Some("7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v".into())
690            );
691            assert_eq!(
692                digest.opaque,
693                Some("FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS".into())
694            );
695            assert_eq!(digest.stale, None);
696            assert_eq!(digest.algorithm, Some(Algorithm::Sha256));
697            assert_eq!(digest.qop, Some(vec![Qop::Auth, Qop::AuthInt]));
698            assert_eq!(digest.userhash, None);
699        }
700
701        #[test]
702        fn test_roundtrip_digest() {
703            let digest = DigestChallenge {
704                realm: Some("http-auth@example.org".into()),
705                domain: None,
706                nonce: Some("7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v".into()),
707                opaque: Some("FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS".into()),
708                stale: None,
709                algorithm: Some(Algorithm::Sha256),
710                qop: Some(vec![Qop::Auth, Qop::AuthInt]),
711                userhash: None,
712            };
713            let auth = WwwAuthenticate::new(digest.clone());
714            let data = format!("{}", auth);
715            let auth = WwwAuthenticate::parse_header(&Raw::from(data)).unwrap();
716            let digest_tripped = auth.get::<DigestChallenge>().unwrap().swap_remove(0);
717            assert_eq!(digest, digest_tripped);
718        }
719    }
720}
721
722mod parser {
723    use super::raw::{ChallengeFields, RawChallenge};
724    use hyperx::{Error, Result};
725    use std::cell::Cell;
726    use std::str::from_utf8_unchecked;
727
728    pub struct Stream<'a>(Cell<usize>, &'a [u8]);
729
730    pub fn is_ws(c: u8) -> bool {
731        // See https://tools.ietf.org/html/rfc7230#section-3.2.3
732        b"\t ".contains(&c)
733    }
734
735    pub fn is_token_char(c: u8) -> bool {
736        // See https://tools.ietf.org/html/rfc7230#section-3.2.6
737        br#"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!#$%&'*+-.^_`|~"#
738            .contains(&c)
739    }
740
741    pub fn is_obs_text(c: u8) -> bool {
742        // See https://tools.ietf.org/html/rfc7230#section-3.2.6
743        // u8 is always under 0xFF
744        0x80 <= c
745    }
746
747    pub fn is_vchar(c: u8) -> bool {
748        // consult the ASCII definition
749        (0x21..=0x7E).contains(&c)
750    }
751
752    pub fn is_qdtext(c: u8) -> bool {
753        // See https://tools.ietf.org/html/rfc7230#section-3.2.6
754        b"\t \x21".contains(&c) || (0x23..=0x5B).contains(&c) || (0x5D <= 0x7E) || is_obs_text(c)
755    }
756
757    pub fn is_quoting(c: u8) -> bool {
758        b"\t ".contains(&c) || is_vchar(c) || is_obs_text(c)
759    }
760
761    impl<'a> Stream<'a> {
762        pub fn new(data: &'a [u8]) -> Self {
763            Stream(Cell::from(0), data)
764        }
765
766        pub fn inc(&self, i: usize) {
767            let pos = self.pos();
768            self.0.set(pos + i);
769        }
770
771        pub fn pos(&self) -> usize {
772            self.0.get()
773        }
774
775        pub fn is_end(&self) -> bool {
776            self.1.len() <= self.pos()
777        }
778
779        pub fn cur(&self) -> u8 {
780            self.1[self.pos()]
781        }
782
783        pub fn skip_a(&self, c: u8) -> Result<()> {
784            if self.cur() == c {
785                self.inc(1);
786                Ok(())
787            } else {
788                Err(Error::Header)
789            }
790        }
791        pub fn skip_a_next(&self, c: u8) -> Result<()> {
792            self.skip_ws()?;
793            if self.is_end() {
794                return Err(Error::Header);
795            }
796            self.skip_a(c)
797        }
798
799        pub fn take_while<F>(&self, f: F) -> Result<&[u8]>
800        where
801            F: Fn(u8) -> bool,
802        {
803            let start = self.pos();
804            while !self.is_end() && f(self.cur()) {
805                self.inc(1);
806            }
807            Ok(&self.1[start..self.pos()])
808        }
809
810        pub fn take_while1<F>(&self, f: F) -> Result<&[u8]>
811        where
812            F: Fn(u8) -> bool,
813        {
814            self.take_while(f).and_then(|b| {
815                if b.is_empty() {
816                    Err(Error::Header)
817                } else {
818                    Ok(b)
819                }
820            })
821        }
822
823        pub fn r#try<F, T>(&self, f: F) -> Result<T>
824        where
825            F: FnOnce() -> Result<T>,
826        {
827            let init = self.pos();
828            match f() {
829                ok @ Ok(_) => ok,
830                err @ Err(_) => {
831                    self.0.set(init);
832                    err
833                }
834            }
835        }
836
837        pub fn skip_ws(&self) -> Result<()> {
838            self.take_while(is_ws).map(|_| ())
839        }
840
841        pub fn skip_next_comma(&self) -> Result<()> {
842            self.skip_a_next(b',')
843        }
844
845        pub fn skip_field_sep(&self) -> Result<()> {
846            self.skip_ws()?;
847            if self.is_end() {
848                return Ok(());
849            }
850            self.skip_next_comma()?;
851            while self.skip_next_comma().is_ok() {}
852            self.skip_ws()?;
853            Ok(())
854        }
855
856        pub fn token(&self) -> Result<&str> {
857            self.take_while1(is_token_char)
858                .map(|s| unsafe { from_utf8_unchecked(s) })
859        }
860
861        pub fn next_token(&self) -> Result<&str> {
862            self.skip_ws()?;
863            self.token()
864        }
865
866        pub fn quoted_string(&self) -> Result<String> {
867            // See https://tools.ietf.org/html/rfc7230#section-3.2.6
868            if self.is_end() {
869                return Err(Error::Header);
870            }
871
872            if self.cur() != b'"' {
873                return Err(Error::Header);
874            }
875            self.inc(1);
876            let mut s = Vec::new();
877            while !self.is_end() && self.cur() != b'"' {
878                if self.cur() == b'\\' {
879                    self.inc(1);
880                    if is_quoting(self.cur()) {
881                        s.push(self.cur());
882                        self.inc(1);
883                    } else {
884                        return Err(Error::Header);
885                    }
886                } else if is_qdtext(self.cur()) {
887                    s.push(self.cur());
888                    self.inc(1);
889                } else {
890                    return Err(Error::Header);
891                }
892            }
893            if self.is_end() {
894                return Err(Error::Header);
895            } else {
896                debug_assert!(self.cur() == b'"');
897                self.inc(1);
898            }
899            String::from_utf8(s).map_err(|_| Error::Header)
900        }
901
902        pub fn token68(&self) -> Result<&str> {
903            let start = self.pos();
904            // See https://tools.ietf.org/html/rfc7235#section-2.1
905            self.take_while1(|c| {
906                b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-._~+/".contains(&c)
907            })?;
908            self.take_while(|c| c == b'=')?;
909            Ok(unsafe { from_utf8_unchecked(&self.1[start..self.pos()]) })
910        }
911
912        pub fn kv_token(&self) -> Result<(&str, &str)> {
913            let k = self.token()?;
914            self.skip_a_next(b'=')?;
915            self.skip_ws()?;
916            let v = self.token()?;
917            Ok((k, v))
918        }
919
920        pub fn kv_quoted(&self) -> Result<(&str, String)> {
921            let k = self.token()?;
922            self.skip_a_next(b'=')?;
923            self.skip_ws()?;
924            let v = self.quoted_string()?;
925            Ok((k, v))
926        }
927
928        pub fn field(&self) -> Result<(String, String)> {
929            self.r#try(|| self.kv_token().map(|(k, v)| (k.to_string(), v.to_string())))
930                .or_else(|_| self.kv_quoted().map(|(k, v)| (k.to_string(), v)))
931        }
932
933        pub fn raw_token68(&self) -> Result<RawChallenge> {
934            let ret = self
935                .token68()
936                .map(ToString::to_string)
937                .map(RawChallenge::Token68)?;
938            self.skip_field_sep()?;
939            Ok(ret)
940        }
941
942        pub fn raw_fields(&self) -> Result<RawChallenge> {
943            let mut map = ChallengeFields::new();
944            loop {
945                match self.r#try(|| self.field()) {
946                    Err(_) => return Ok(RawChallenge::Fields(map)),
947                    Ok((k, v)) => {
948                        if self.skip_field_sep().is_ok() {
949                            if map.insert(k, v).is_some() {
950                                // field key must not be duplicated
951                                return Err(Error::Header);
952                            }
953                            if self.is_end() {
954                                return Ok(RawChallenge::Fields(map));
955                            }
956                        } else {
957                            return Err(Error::Header);
958                        }
959                    }
960                }
961            }
962        }
963
964        pub fn challenge(&self) -> Result<(String, RawChallenge)> {
965            let scheme = self.next_token()?;
966            self.take_while1(is_ws)?;
967            let challenge = self
968                .r#try(|| self.raw_token68())
969                .or_else(|_| self.raw_fields())?;
970            Ok((scheme.to_string(), challenge))
971        }
972    }
973
974    #[test]
975    fn test_parese_quoted_field() {
976        let b = b"realm=\"secret zone\"";
977        let stream = Stream::new(b);
978        let (k, v) = stream.field().unwrap();
979        assert_eq!(k, "realm");
980        assert_eq!(v, "secret zone");
981        assert!(stream.is_end());
982    }
983
984    #[test]
985    fn test_parese_quoted_field_nonvchars() {
986        let b = b"realm=\"secret zone\t\xe3\x8a\x99\"";
987        let stream = Stream::new(b);
988        let (k, v) = stream.field().unwrap();
989        assert_eq!(k, "realm");
990        assert_eq!(v, "secret zone\t㊙");
991        assert!(stream.is_end());
992    }
993
994    #[test]
995    fn test_parese_token_field() {
996        let b = b"algorithm=MD5";
997        let stream = Stream::new(b);
998        let (k, v) = stream.field().unwrap();
999        assert_eq!(k, "algorithm");
1000        assert_eq!(v, "MD5");
1001        assert!(stream.is_end());
1002    }
1003
1004    #[test]
1005    fn test_parese_raw_quoted_fields() {
1006        let b = b"realm=\"secret zone\"";
1007        let stream = Stream::new(b);
1008        match stream.raw_fields().unwrap() {
1009            RawChallenge::Token68(_) => panic!(),
1010            RawChallenge::Fields(fields) => {
1011                assert_eq!(fields.len(), 1);
1012                assert_eq!(fields.get("realm").unwrap(), "secret zone");
1013            }
1014        }
1015        assert!(stream.is_end());
1016    }
1017
1018    #[test]
1019    fn test_parese_raw_token_fields() {
1020        let b = b"algorithm=MD5";
1021        let stream = Stream::new(b);
1022        match stream.raw_fields().unwrap() {
1023            RawChallenge::Token68(_) => panic!(),
1024            RawChallenge::Fields(fields) => {
1025                assert_eq!(fields.len(), 1);
1026                assert_eq!(fields.get("algorithm").unwrap(), "MD5");
1027            }
1028        }
1029        assert!(stream.is_end());
1030    }
1031    #[test]
1032    fn test_parese_token68() {
1033        let b = b"auea1./+=";
1034        let stream = Stream::new(b);
1035        let token = stream.token68().unwrap();
1036        assert_eq!(token, "auea1./+=");
1037        assert!(stream.is_end());
1038    }
1039
1040    #[test]
1041    fn test_parese_raw_token68() {
1042        let b = b"auea1./+=";
1043        let stream = Stream::new(b);
1044        match stream.raw_token68().unwrap() {
1045            RawChallenge::Token68(token) => assert_eq!(token, "auea1./+="),
1046            RawChallenge::Fields(_) => panic!(),
1047        }
1048        assert!(stream.is_end());
1049    }
1050
1051    #[test]
1052    fn test_parese_challenge1() {
1053        let b = b"Token abceaqj13-.+=";
1054        let stream = Stream::new(b);
1055        match stream.challenge().unwrap() {
1056            (scheme, RawChallenge::Token68(token)) => {
1057                assert_eq!(scheme, "Token");
1058                assert_eq!(token, "abceaqj13-.+=");
1059            }
1060            (_, RawChallenge::Fields(_)) => panic!(),
1061        }
1062        assert!(stream.is_end());
1063    }
1064
1065    #[test]
1066    fn test_parese_challenge2() {
1067        let b = b"Basic realm=\"secret zone\"";
1068        let stream = Stream::new(b);
1069        match stream.challenge().unwrap() {
1070            (_, RawChallenge::Token68(_)) => panic!(),
1071            (scheme, RawChallenge::Fields(fields)) => {
1072                assert_eq!(scheme, "Basic");
1073                assert_eq!(fields.len(), 1);
1074                assert_eq!(fields.get("realm").unwrap(), "secret zone");
1075            }
1076        }
1077        assert!(stream.is_end());
1078    }
1079
1080    #[test]
1081    fn test_parese_challenge3() {
1082        let b = b"Bearer token=aeub8_";
1083        let stream = Stream::new(b);
1084        match stream.challenge().unwrap() {
1085            (_, RawChallenge::Token68(_)) => panic!(),
1086            (scheme, RawChallenge::Fields(fields)) => {
1087                assert_eq!(scheme, "Bearer");
1088                assert_eq!(fields.len(), 1);
1089                assert_eq!(fields.get("token").unwrap(), "aeub8_");
1090            }
1091        }
1092        assert!(stream.is_end());
1093    }
1094
1095    #[test]
1096    fn test_parese_challenge4() {
1097        let b = b"Bearer token=aeub8_, user=\"fooo\"";
1098        let stream = Stream::new(b);
1099        match stream.challenge().unwrap() {
1100            (_, RawChallenge::Token68(_)) => panic!(),
1101            (scheme, RawChallenge::Fields(fields)) => {
1102                assert_eq!(scheme, "Bearer");
1103                assert_eq!(fields.len(), 2);
1104                assert_eq!(fields.get("token").unwrap(), "aeub8_");
1105                assert_eq!(fields.get("user").unwrap(), "fooo");
1106            }
1107        }
1108        assert!(stream.is_end());
1109    }
1110
1111    #[test]
1112    fn test_parese_challenge5() {
1113        let b = b"Bearer user=\"fooo\",,, token=aeub8_,,";
1114        let stream = Stream::new(b);
1115        match stream.challenge().unwrap() {
1116            (_, RawChallenge::Token68(_)) => panic!(),
1117            (scheme, RawChallenge::Fields(fields)) => {
1118                assert_eq!(scheme, "Bearer");
1119                assert_eq!(fields.len(), 2);
1120                assert_eq!(fields.get("token").unwrap(), "aeub8_");
1121                assert_eq!(fields.get("user").unwrap(), "fooo");
1122            }
1123        }
1124        assert!(stream.is_end());
1125    }
1126
1127    #[test]
1128    #[should_panic]
1129    fn test_parse_null() {
1130        let b = b"";
1131        let stream = Stream::new(b);
1132        println!("{:?}", stream.challenge().unwrap());
1133    }
1134}
1135
1136#[cfg(test)]
1137mod tests {
1138    use super::*;
1139
1140    use hyperx::header::Raw;
1141
1142    #[test]
1143    fn test_www_authenticate_multiple_headers() {
1144        let input1 = br#"Digest realm="http-auth@example.org", qop="auth, auth-int", algorithm=SHA-256, nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS""#.to_vec();
1145        let input2 = br#"Digest realm="http-auth@example.org", qop="auth, auth-int", algorithm=MD5, nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS""#.to_vec();
1146        let input = Raw::from(vec![input1, input2]);
1147
1148        let auth = WwwAuthenticate::parse_header(&input).unwrap();
1149        let digests = auth.get::<DigestChallenge>().unwrap();
1150        assert!(digests.contains(&DigestChallenge {
1151            realm: Some("http-auth@example.org".into()),
1152            qop: Some(vec![Qop::Auth, Qop::AuthInt]),
1153            algorithm: Some(Algorithm::Sha256),
1154            nonce: Some("7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v".into()),
1155            opaque: Some("FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS".into()),
1156            domain: None,
1157            stale: None,
1158            userhash: None,
1159        }));
1160
1161        assert!(digests.contains(&DigestChallenge {
1162            realm: Some("http-auth@example.org".into()),
1163            qop: Some(vec![Qop::Auth, Qop::AuthInt]),
1164            algorithm: Some(Algorithm::Md5),
1165            nonce: Some("7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v".into()),
1166            opaque: Some("FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS".into()),
1167            domain: None,
1168            stale: None,
1169            userhash: None,
1170        }));
1171    }
1172}