1extern 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#[derive(Debug, Clone)]
77pub struct WwwAuthenticate(HashMap<UniCase<CowStr>, Vec<RawChallenge>>);
78
79pub 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 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 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 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 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 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 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 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 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 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 #[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 pub fn len(&self) -> usize {
282 self.0.len()
283 }
284 pub fn is_empty(&self) -> bool {
285 self.0.is_empty()
286 }
287 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 #[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 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
383 pub struct BasicChallenge {
384 pub realm: String,
386 }
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 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 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
459 pub struct DigestChallenge {
460 pub realm: Option<String>,
462 pub domain: Option<Vec<Url>>,
464 pub nonce: Option<String>,
466 pub opaque: Option<String>,
468 pub stale: Option<bool>,
471 pub algorithm: Option<Algorithm>,
474 pub qop: Option<Vec<Qop>>,
476 pub userhash: Option<bool>,
481 }
482
483 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
485 pub enum Algorithm {
486 Md5,
488 Md5Sess,
490 Sha512Trunc256,
492 Sha512Trunc256Sess,
494 Sha256,
496 Sha256Sess,
498 Other(String),
500 }
501
502 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
504 pub enum Qop {
505 Auth,
507 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 userhash,
595 })
596 }
597 }
598 }
599 fn into_raw(self) -> RawChallenge {
600 let mut map = ChallengeFields::new();
601 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 b"\t ".contains(&c)
733 }
734
735 pub fn is_token_char(c: u8) -> bool {
736 br#"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!#$%&'*+-.^_`|~"#
738 .contains(&c)
739 }
740
741 pub fn is_obs_text(c: u8) -> bool {
742 0x80 <= c
745 }
746
747 pub fn is_vchar(c: u8) -> bool {
748 (0x21..=0x7E).contains(&c)
750 }
751
752 pub fn is_qdtext(c: u8) -> bool {
753 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 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 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 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}