1use core::{fmt, iter::Peekable};
24
25use crate::{
26 class,
27 error::{ErrorKind, ParseError, Result, WriteError, err},
28 limits::Limits,
29 list,
30 scan::control_kind,
31 text::{Word, is_token, parse_word, skip_ows, trim_ows},
32 write::{CountSink, Sink, reserve, unique_names, write_quoted, write_word},
33};
34
35fn is_token68(s: &[u8]) -> bool {
38 let body = s.iter().take_while(|&&b| class::is_token68_char(b)).count();
39 body > 0
40 && s.get(body..)
41 .is_some_and(|pad| pad.iter().all(|&b| b == b'='))
42}
43
44fn auth_param(e: &[u8]) -> Result<(&[u8], Word<'_>)> {
46 let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
47 if n == 0 {
48 return Err(refused(e, 0, ErrorKind::NotToken));
49 }
50 let name = e.get(..n).unwrap_or(&[]);
51 let mut i = n + skip_ows(e.get(n..).unwrap_or(&[]));
52 if e.get(i) != Some(&b'=') {
53 return Err(refused(e, i, ErrorKind::MissingValue));
54 }
55 i += 1;
56 i += skip_ows(e.get(i..).unwrap_or(&[]));
57 let (w, used) = parse_word(e.get(i..).unwrap_or(&[])).map_err(|x| match x.kind() {
58 ErrorKind::Empty => refused(e, i, ErrorKind::MissingValue),
59 _ => x.shifted(i),
60 })?;
61 if i + used != e.len() {
62 return Err(refused(e, i + used, ErrorKind::TrailingData));
63 }
64 Ok((name, w))
65}
66
67fn refused(e: &[u8], at: usize, kind: ErrorKind) -> ParseError {
68 match e.get(at) {
69 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
70 _ => ParseError::new(kind, at),
71 }
72}
73
74fn is_param_start(e: &[u8]) -> bool {
77 let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
78 n > 0 && e.get(n + skip_ows(e.get(n..).unwrap_or(&[]))) == Some(&b'=')
79}
80
81#[derive(Clone, Copy, PartialEq, Eq, Hash)]
85pub struct AuthParams<'a> {
86 text: &'a [u8],
87 count: usize,
88}
89
90impl<'a> AuthParams<'a> {
91 pub fn iter(&self) -> impl Iterator<Item = (&'a [u8], Word<'a>)> + use<'a> {
93 list::parse(self.text, &Limits::DEFAULT.max_list_members(u16::MAX))
94 .into_iter()
95 .flat_map(|l| l.into_iter())
96 .filter_map(|e| auth_param(e).ok())
97 }
98
99 #[must_use]
101 pub fn get(&self, name: &str) -> Option<Word<'a>> {
102 self.iter()
103 .find(|(n, _)| n.eq_ignore_ascii_case(name.as_bytes()))
104 .map(|(_, v)| v)
105 }
106
107 #[must_use]
109 pub const fn len(&self) -> usize {
110 self.count
111 }
112
113 #[must_use]
115 pub const fn is_empty(&self) -> bool {
116 self.count == 0
117 }
118}
119
120impl fmt::Debug for AuthParams<'_> {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 f.write_str("AuthParams(")?;
123 f.debug_list()
124 .entries(
125 self.iter()
126 .map(|(n, _)| core::str::from_utf8(n).unwrap_or("?")),
127 )
128 .finish()?;
129 f.write_str(")")
130 }
131}
132
133#[derive(Clone, Copy, PartialEq, Eq, Hash)]
135pub enum AuthPayload<'a> {
136 None,
138 Token68(&'a [u8]),
140 Params(AuthParams<'a>),
142}
143
144impl fmt::Debug for AuthPayload<'_> {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 match self {
147 AuthPayload::None => f.write_str("None"),
148 AuthPayload::Token68(_) => f.write_str("Token68(..)"),
149 AuthPayload::Params(p) => fmt::Debug::fmt(p, f),
150 }
151 }
152}
153
154#[derive(Clone, Copy, PartialEq, Eq, Hash)]
158pub struct Credentials<'a> {
159 scheme: &'a [u8],
160 payload: AuthPayload<'a>,
161}
162
163impl<'a> Credentials<'a> {
164 #[must_use]
166 pub const fn scheme(&self) -> &'a [u8] {
167 self.scheme
168 }
169
170 #[must_use]
172 pub fn scheme_is(&self, s: &str) -> bool {
173 self.scheme.eq_ignore_ascii_case(s.as_bytes())
174 }
175
176 #[must_use]
178 pub const fn payload(&self) -> AuthPayload<'a> {
179 self.payload
180 }
181}
182
183impl fmt::Debug for Credentials<'_> {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 f.debug_struct("Credentials")
186 .field("scheme", &core::str::from_utf8(self.scheme).unwrap_or("?"))
187 .finish_non_exhaustive()
188 }
189}
190
191fn check_params(text: &[u8], at: usize, limits: &Limits) -> Result<usize> {
194 let l = list::parse(text, limits).map_err(|e| e.shifted(at))?;
195 let mut count = 0usize;
196 for e in l.iter() {
197 let off = at + (e.as_ptr() as usize).saturating_sub(text.as_ptr() as usize);
198 let (name, _) = auth_param(e).map_err(|x| x.shifted(off))?;
199 let earlier = text
202 .get(..(e.as_ptr() as usize).saturating_sub(text.as_ptr() as usize))
203 .unwrap_or(&[]);
204 let seen = list::parse(earlier, limits)
205 .into_iter()
206 .flat_map(|x| x.into_iter())
207 .filter_map(|x| auth_param(x).ok())
208 .any(|(n, _)| n.eq_ignore_ascii_case(name));
209 if seen {
210 return err(ErrorKind::DuplicateParameter, off);
211 }
212 count += 1;
213 if count > usize::from(limits.params) {
214 return err(ErrorKind::TooManyMembers, off);
215 }
216 }
217 Ok(count)
218}
219
220pub fn parse_credentials<'a>(value: &'a [u8], limits: &Limits) -> Result<Credentials<'a>> {
240 let lead = skip_ows(value);
241 let v = trim_ows(value);
242 let n = v.iter().take_while(|&&b| class::is_tchar(b)).count();
243 if n == 0 {
244 return Err(refused(v, 0, ErrorKind::NotToken).shifted(lead));
245 }
246 let scheme = v.get(..n).unwrap_or(&[]);
247 let rest = v.get(n..).unwrap_or(&[]);
248 if rest.is_empty() {
249 return Ok(Credentials {
250 scheme,
251 payload: AuthPayload::None,
252 });
253 }
254 let spaces = rest.iter().take_while(|&&b| b == b' ').count();
255 if spaces == 0 {
256 return Err(refused(v, n, ErrorKind::NotToken).shifted(lead));
257 }
258 let at = n + spaces;
259 let body = v.get(at..).unwrap_or(&[]);
260 if is_token68(body) {
261 return Ok(Credentials {
262 scheme,
263 payload: AuthPayload::Token68(body),
264 });
265 }
266 if !is_param_start(body) {
267 let t68 = body
269 .iter()
270 .take_while(|&&b| class::is_token68_char(b) || b == b'=')
271 .count();
272 return Err(refused(v, at + t68, ErrorKind::TrailingData).shifted(lead));
273 }
274 let count = check_params(body, lead + at, limits)?;
275 Ok(Credentials {
276 scheme,
277 payload: AuthPayload::Params(AuthParams { text: body, count }),
278 })
279}
280
281pub fn bearer(value: &[u8]) -> Result<Option<&[u8]>> {
291 let c = parse_credentials(value, &Limits::DEFAULT)?;
292 if !c.scheme_is("bearer") {
293 return Ok(None);
294 }
295 match c.payload {
296 AuthPayload::Token68(t) => Ok(Some(t)),
297 AuthPayload::None => err(ErrorKind::Empty, value.len()),
298 AuthPayload::Params(p) => {
299 let at = (p.text.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
300 err(ErrorKind::InvalidByte, at)
301 }
302 }
303}
304
305pub fn basic(value: &[u8]) -> Result<Option<&[u8]>> {
315 let c = parse_credentials(value, &Limits::DEFAULT)?;
316 if !c.scheme_is("basic") {
317 return Ok(None);
318 }
319 let t = match c.payload {
320 AuthPayload::Token68(t) => t,
321 AuthPayload::None => return err(ErrorKind::Empty, value.len()),
322 AuthPayload::Params(p) => {
323 let at = (p.text.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
324 return err(ErrorKind::InvalidBase64, at);
325 }
326 };
327 let at = (t.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
328 let body = t.iter().take_while(|&&b| class::is_base64_char(b)).count();
329 let pad = t.len() - body;
330 if body < t.len() && t.get(body) != Some(&b'=') || pad > 2 || t.len() % 4 != 0 {
331 return err(ErrorKind::InvalidBase64, at + body);
332 }
333 Ok(Some(t))
334}
335
336#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
338pub struct Challenge<'a> {
339 scheme: &'a [u8],
340 payload: AuthPayload<'a>,
341}
342
343impl<'a> Challenge<'a> {
344 #[must_use]
346 pub const fn scheme(&self) -> &'a [u8] {
347 self.scheme
348 }
349
350 #[must_use]
352 pub fn scheme_is(&self, s: &str) -> bool {
353 self.scheme.eq_ignore_ascii_case(s.as_bytes())
354 }
355
356 #[must_use]
358 pub const fn payload(&self) -> AuthPayload<'a> {
359 self.payload
360 }
361
362 #[must_use]
364 pub fn param(&self, name: &str) -> Option<Word<'a>> {
365 match self.payload {
366 AuthPayload::Params(p) => p.get(name),
367 _ => None,
368 }
369 }
370}
371
372#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
374pub struct Challenges<'a> {
375 list: list::List<'a>,
376 value: &'a [u8],
377 count: usize,
378}
379
380impl<'a> Challenges<'a> {
381 #[must_use]
383 pub fn iter(&self) -> ChallengeIter<'a> {
384 ChallengeIter {
385 value: self.value,
386 elements: self.list.iter().peekable(),
387 }
388 }
389
390 #[must_use]
392 pub const fn len(&self) -> usize {
393 self.count
394 }
395
396 #[must_use]
398 pub const fn is_empty(&self) -> bool {
399 self.count == 0
400 }
401}
402
403#[derive(Clone, Debug)]
405pub struct ChallengeIter<'a> {
406 value: &'a [u8],
407 elements: Peekable<list::Elements<'a>>,
408}
409
410fn challenge_start(e: &[u8]) -> (&[u8], &[u8]) {
412 let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
413 let scheme = e.get(..n).unwrap_or(&[]);
414 let rest = e.get(n..).unwrap_or(&[]);
415 let rest = rest
416 .get(rest.iter().take_while(|&&b| b == b' ').count()..)
417 .unwrap_or(&[]);
418 (scheme, rest)
419}
420
421impl<'a> Iterator for ChallengeIter<'a> {
422 type Item = Challenge<'a>;
423
424 fn next(&mut self) -> Option<Challenge<'a>> {
425 let e = self.elements.next()?;
426 let (scheme, rest) = challenge_start(e);
427 if rest.is_empty() {
428 return Some(Challenge {
429 scheme,
430 payload: AuthPayload::None,
431 });
432 }
433 if is_token68(rest) {
434 return Some(Challenge {
435 scheme,
436 payload: AuthPayload::Token68(rest),
437 });
438 }
439 let start = (rest.as_ptr() as usize).saturating_sub(self.value.as_ptr() as usize);
442 let mut end = start + rest.len();
443 let mut count = 1usize;
444 while let Some(next) = self.elements.peek() {
445 if !is_param_start(next) {
446 break;
447 }
448 end =
449 (next.as_ptr() as usize).saturating_sub(self.value.as_ptr() as usize) + next.len();
450 count += 1;
451 let _skipped = self.elements.next();
452 }
453 let text = self.value.get(start..end).unwrap_or(&[]);
454 Some(Challenge {
455 scheme,
456 payload: AuthPayload::Params(AuthParams { text, count }),
457 })
458 }
459}
460
461pub fn parse_challenges<'a>(value: &'a [u8], limits: &Limits) -> Result<Challenges<'a>> {
490 let l = list::parse(value, limits)?;
491 let mut count = 0usize;
492 let mut elements = l.iter().peekable();
493 while let Some(e) = elements.next() {
494 let at = (e.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
495 if is_param_start(e) {
496 return err(ErrorKind::ExpectedSeparator, at);
497 }
498 let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
499 if n == 0 {
500 return Err(refused(e, 0, ErrorKind::NotToken).shifted(at));
501 }
502 let (_, rest) = challenge_start(e);
503 if e.get(n).is_some_and(|&b| b != b' ') {
504 return Err(refused(e, n, ErrorKind::NotToken).shifted(at));
505 }
506 count += 1;
507 if rest.is_empty() || is_token68(rest) {
508 continue;
509 }
510 if !is_param_start(rest) {
511 let off = e.len() - rest.len();
512 return Err(refused(e, off, ErrorKind::TrailingData).shifted(at));
513 }
514 let start = at + (e.len() - rest.len());
517 let mut end = at + e.len();
518 while let Some(next) = elements.peek() {
519 if !is_param_start(next) {
520 break;
521 }
522 end = (next.as_ptr() as usize).saturating_sub(value.as_ptr() as usize) + next.len();
523 let _skipped = elements.next();
524 }
525 let _params = check_params(value.get(start..end).unwrap_or(&[]), start, limits)?;
526 }
527 Ok(Challenges {
528 list: l,
529 value,
530 count,
531 })
532}
533
534fn put_challenge(
535 scheme: &str,
536 params: &[(&str, &str)],
537 out: &mut impl Sink,
538) -> core::result::Result<(), WriteError> {
539 out.put(scheme.as_bytes())?;
540 for (i, (name, value)) in params.iter().enumerate() {
541 out.put(if i == 0 { b" " } else { b", " })?;
542 out.put(name.as_bytes())?;
543 out.put(b"=")?;
544 if name.eq_ignore_ascii_case("realm") {
545 write_quoted(value.as_bytes(), out)?;
546 } else {
547 write_word(value.as_bytes(), out)?;
548 }
549 }
550 Ok(())
551}
552
553pub fn write_challenge(
576 scheme: &str,
577 params: &[(&str, &str)],
578 out: &mut impl Sink,
579) -> core::result::Result<(), WriteError> {
580 if !is_token(scheme.as_bytes()) || params.iter().any(|(n, _)| !is_token(n.as_bytes())) {
581 return Err(WriteError::NotToken);
582 }
583 unique_names(params)?;
584 let mut count = CountSink::new();
585 put_challenge(scheme, params, &mut count)?;
586 reserve(out, count.count())?;
587 put_challenge(scheme, params, out)
588}
589
590pub fn write_challenge_token68(
608 scheme: &str,
609 token68: &[u8],
610 out: &mut impl Sink,
611) -> core::result::Result<(), WriteError> {
612 if !is_token(scheme.as_bytes()) {
613 return Err(WriteError::NotToken);
614 }
615 if !is_token68(token68) {
616 let at = token68
617 .iter()
618 .position(|&b| !(class::is_token68_char(b) || b == b'='))
619 .unwrap_or(0);
620 return Err(WriteError::forbidden(at));
621 }
622 reserve(out, scheme.len() + 1 + token68.len())?;
623 out.put(scheme.as_bytes())?;
624 out.put(b" ")?;
625 out.put(token68)?;
626 Ok(())
627}
628
629#[cfg(test)]
630mod tests {
631 use std::{format, vec::Vec};
632
633 use super::*;
634
635 #[test]
636 fn test_bearer_cases() {
637 assert_eq!(bearer(b"Bearer abc").unwrap(), Some(&b"abc"[..]));
638 assert_eq!(bearer(b"bearer abc").unwrap(), Some(&b"abc"[..]));
639 assert_eq!(bearer(b"Bearer abc").unwrap(), Some(&b"abc"[..]));
640 assert_eq!(bearer(b"Bearer abc==").unwrap(), Some(&b"abc=="[..]));
641 assert_eq!(bearer(b"Bearer").unwrap_err().kind(), ErrorKind::Empty);
642 assert_eq!(
643 bearer(b"Bearer abc def").unwrap_err().kind(),
644 ErrorKind::TrailingData
645 );
646 assert_eq!(
647 bearer(b"Bearer a=b").unwrap_err().kind(),
648 ErrorKind::InvalidByte
649 );
650 assert_eq!(
651 bearer(b"Bearer\tabc").unwrap_err().kind(),
652 ErrorKind::NotToken
653 );
654 assert_eq!(bearer(b"").unwrap_err().kind(), ErrorKind::NotToken);
655 assert_eq!(bearer(b"Digest a=b").unwrap(), None);
656 }
657
658 #[test]
659 fn test_basic_cases() {
660 assert_eq!(
661 basic(b"Basic dXNlcjpwYXNz").unwrap(),
662 Some(&b"dXNlcjpwYXNz"[..])
663 );
664 assert_eq!(basic(b"Basic YQ==").unwrap(), Some(&b"YQ=="[..]));
665 assert_eq!(basic(b"Basic").unwrap_err().kind(), ErrorKind::Empty);
666 assert_eq!(
667 basic(b"Basic YQ").unwrap_err().kind(),
668 ErrorKind::InvalidBase64
669 );
670 assert_eq!(
671 basic(b"Basic a-b_").unwrap_err().kind(),
672 ErrorKind::InvalidBase64
673 );
674 assert_eq!(
675 basic(b"Basic YQ===").unwrap_err().kind(),
676 ErrorKind::InvalidBase64
677 );
678 assert_eq!(basic(b"Bearer x").unwrap(), None);
679 }
680
681 #[test]
682 fn test_credentials_params_and_duplicates() {
683 let l = Limits::DEFAULT;
684 let c = parse_credentials(b"Digest username = \"a\" , realm=r, ", &l).unwrap();
685 let AuthPayload::Params(p) = c.payload() else {
686 panic!("params")
687 };
688 assert_eq!(p.len(), 2);
689 assert!(p.get("USERNAME").unwrap().eq_bytes(b"a"));
690 assert_eq!(
691 parse_credentials(b"Digest a=b, A=c", &l)
692 .unwrap_err()
693 .kind(),
694 ErrorKind::DuplicateParameter
695 );
696 assert_eq!(
697 parse_credentials(b"Negotiate token68===", &l)
698 .unwrap()
699 .payload(),
700 AuthPayload::Token68(b"token68===")
701 );
702 assert_eq!(
703 parse_credentials(b"Digest a", &l).unwrap().payload(),
704 AuthPayload::Token68(b"a")
705 );
706 assert_eq!(
707 parse_credentials(b"Digest a b", &l).unwrap_err().kind(),
708 ErrorKind::TrailingData
709 );
710 assert_eq!(
712 parse_credentials(b"Digest a=", &l).unwrap().payload(),
713 AuthPayload::Token68(b"a=")
714 );
715 assert_eq!(
716 parse_credentials(b"Digest a=, b=1", &l).unwrap_err().kind(),
717 ErrorKind::MissingValue
718 );
719 assert_eq!(
720 parse_credentials(b"Digest a=\"x", &l).unwrap_err().kind(),
721 ErrorKind::UnterminatedQuote
722 );
723 }
724
725 #[test]
726 fn test_credentials_debug_redacted() {
727 let c = parse_credentials(b"Bearer s3cr3t-token", &Limits::DEFAULT).unwrap();
728 let d = format!("{c:?} {:?}", c.payload());
729 assert!(!d.contains("s3cr3t"));
730 let p = parse_credentials(b"Digest response=\"s3cr3t\"", &Limits::DEFAULT).unwrap();
731 let d = format!("{:?}", p.payload());
732 assert_eq!(d, "AuthParams([\"response\"])");
733 }
734
735 #[test]
736 fn test_challenges_rfc_examples() {
737 let l = Limits::DEFAULT;
738 let names = |v: &[u8]| -> Vec<Vec<u8>> {
739 parse_challenges(v, &l)
740 .unwrap()
741 .iter()
742 .map(|c| c.scheme().to_vec())
743 .collect()
744 };
745 assert_eq!(
746 names(b"Basic realm=\"a\", Bearer"),
747 [b"Basic".to_vec(), b"Bearer".to_vec()]
748 );
749 let c = parse_challenges(b"Bearer realm=\"a\", error=\"invalid_token\"", &l).unwrap();
750 let one: Vec<_> = c.iter().collect();
751 assert_eq!(one.len(), 1);
752 assert!(one[0].param("error").unwrap().eq_bytes(b"invalid_token"));
753 assert_eq!(
754 names(b"Negotiate abc==, Basic realm=x"),
755 [b"Negotiate".to_vec(), b"Basic".to_vec()]
756 );
757 assert_eq!(
758 names(b"A, , B, ,C"),
759 [b"A".to_vec(), b"B".to_vec(), b"C".to_vec()]
760 );
761 let t68 = parse_challenges(b"Negotiate abc==", &l).unwrap();
762 assert_eq!(
763 t68.iter().next().unwrap().payload(),
764 AuthPayload::Token68(b"abc==")
765 );
766 }
767
768 #[test]
769 fn test_challenge_refusals() {
770 let l = Limits::DEFAULT;
771 let k = |v: &[u8]| parse_challenges(v, &l).unwrap_err().kind();
772 assert_eq!(k(b"Basic realm=a, realm=b"), ErrorKind::DuplicateParameter);
773 assert_eq!(k(b"realm=a"), ErrorKind::ExpectedSeparator);
774 assert_eq!(k(b"Basic\trealm=a"), ErrorKind::NotToken);
775 assert_eq!(k(b"Basic abc def"), ErrorKind::TrailingData);
776 assert!(parse_challenges(b"Basic realm=a, Digest realm=b", &l).is_ok());
777 }
778
779 #[test]
780 fn test_challenge_writers_round_trip() {
781 let mut buf = [0u8; 128];
782 let mut out = crate::SliceSink::new(&mut buf);
783 write_challenge(
784 "Bearer",
785 &[("realm", "api"), ("scope", "read write")],
786 &mut out,
787 )
788 .unwrap();
789 let c = parse_challenges(out.written(), &Limits::DEFAULT).unwrap();
790 let first = c.iter().next().unwrap();
791 assert!(first.param("realm").unwrap().eq_bytes(b"api"));
792 assert!(first.param("scope").unwrap().eq_bytes(b"read write"));
793 assert_eq!(
794 write_challenge("Bad scheme", &[], &mut out),
795 Err(WriteError::NotToken)
796 );
797 assert_eq!(
798 write_challenge("Basic", &[("realm", "a"), ("Realm", "b")], &mut out),
799 Err(WriteError::DuplicateName)
800 );
801 assert_eq!(
802 write_challenge("Basic", &[("realm", "a\r\n")], &mut out),
803 Err(WriteError::Forbidden { at: 1 })
804 );
805 assert_eq!(
806 write_challenge_token68("Negotiate", b"a b", &mut out),
807 Err(WriteError::Forbidden { at: 1 })
808 );
809 }
810}