Skip to main content

parse_net/
auth.rs

1//! `Authorization`, `Proxy-Authorization`, `WWW-Authenticate`, and
2//! `Proxy-Authenticate` (RFC 9110 section 11), with the `Bearer` (RFC 6750)
3//! and `Basic` (RFC 7617) payloads.
4//!
5//! Credentials are secrets: `Debug` on [`Credentials`] and [`AuthPayload`]
6//! prints the scheme and parameter names only.
7//!
8//! # Examples
9//!
10//! ```
11//! use parse_net::{self as pn, auth, Limits};
12//!
13//! assert_eq!(pn::bearer(b"bearer  mF_9.B5f-4.1JqM==")?, Some(&b"mF_9.B5f-4.1JqM=="[..]));
14//! assert_eq!(pn::bearer(b"Basic dXNlcjpwYXNz")?, None);
15//! assert_eq!(pn::basic(b"Basic dXNlcjpwYXNz")?, Some(&b"dXNlcjpwYXNz"[..]));
16//!
17//! let c = auth::parse_credentials(b"Digest username=\"a\", realm=\"r\"", &Limits::DEFAULT)?;
18//! assert!(c.scheme_is("digest"));
19//! assert_eq!(format!("{c:?}"), "Credentials { scheme: \"Digest\", .. }");
20//! # Ok::<(), pn::ParseError>(())
21//! ```
22
23use core::{fmt, iter::Peekable};
24
25use crate::{
26    class,
27    error::{ErrorKind, ParseError, Result, WriteError, err},
28    limits::Limits,
29    list,
30    param::{MAX_CHECKED_PARAMS, Names},
31    scan::control_kind,
32    text::{Word, is_token, parse_word, skip_ows, trim_ows},
33    write::{CountSink, Sink, reserve, unique_names, write_quoted, write_word},
34};
35
36/// Whether `s` is a `token68`: `1*( ALPHA / DIGIT / "-" / "." / "_" / "~" /
37/// "+" / "/" ) *"="`.
38fn is_token68(s: &[u8]) -> bool {
39    let body = s.iter().take_while(|&&b| class::is_token68_char(b)).count();
40    body > 0
41        && s.get(body..)
42            .is_some_and(|pad| pad.iter().all(|&b| b == b'='))
43}
44
45/// One `auth-param`: `token BWS "=" BWS ( token / quoted-string )`.
46fn auth_param(e: &[u8]) -> Result<(&[u8], Word<'_>)> {
47    let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
48    if n == 0 {
49        return Err(refused(e, 0, ErrorKind::NotToken));
50    }
51    let name = e.get(..n).unwrap_or(&[]);
52    let mut i = n + skip_ows(e.get(n..).unwrap_or(&[]));
53    if e.get(i) != Some(&b'=') {
54        return Err(refused(e, i, ErrorKind::MissingValue));
55    }
56    i += 1;
57    i += skip_ows(e.get(i..).unwrap_or(&[]));
58    let (w, used) = parse_word(e.get(i..).unwrap_or(&[])).map_err(|x| match x.kind() {
59        ErrorKind::Empty => refused(e, i, ErrorKind::MissingValue),
60        _ => x.shifted(i),
61    })?;
62    if i + used != e.len() {
63        return Err(refused(e, i + used, ErrorKind::TrailingData));
64    }
65    Ok((name, w))
66}
67
68fn refused(e: &[u8], at: usize, kind: ErrorKind) -> ParseError {
69    match e.get(at) {
70        Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
71        _ => ParseError::new(kind, at),
72    }
73}
74
75/// Whether element `e` is an `auth-param` (a token, optional whitespace,
76/// then `=`), as opposed to the start of a challenge.
77fn is_param_start(e: &[u8]) -> bool {
78    let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
79    n > 0 && e.get(n + skip_ows(e.get(n..).unwrap_or(&[]))) == Some(&b'=')
80}
81
82/// Comma-separated `auth-param`s, validated.
83///
84/// `Debug` prints the parameter names only: values can be secrets.
85///
86/// # Examples
87///
88/// ```
89/// use parse_net::{auth, AuthPayload, Limits};
90///
91/// let c = auth::parse_credentials(b"Digest username=\"a\", realm=\"r\"", &Limits::DEFAULT)?;
92/// let AuthPayload::Params(p) = c.payload() else { unreachable!() };
93/// assert_eq!(p.len(), 2);
94/// assert!(p.get("realm").is_some_and(|v| v.eq_bytes(b"r")));
95/// # Ok::<(), parse_net::ParseError>(())
96/// ```
97#[derive(Clone, Copy, PartialEq, Eq, Hash)]
98pub struct AuthParams<'a> {
99    text: &'a [u8],
100    count: usize,
101}
102
103impl<'a> AuthParams<'a> {
104    /// The parameters, in order.
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use parse_net::{auth, AuthPayload, Limits};
110    ///
111    /// let c = auth::parse_credentials(b"Digest username=\"a\", nc=1", &Limits::DEFAULT)?;
112    /// let AuthPayload::Params(p) = c.payload() else { unreachable!() };
113    /// let names: Vec<&[u8]> = p.iter().map(|(n, _)| n).collect();
114    /// assert_eq!(names, [&b"username"[..], b"nc"]);
115    /// # Ok::<(), parse_net::ParseError>(())
116    /// ```
117    pub fn iter(&self) -> impl Iterator<Item = (&'a [u8], Word<'a>)> + use<'a> {
118        list::parse(self.text, &Limits::DEFAULT.max_list_members(u16::MAX))
119            .into_iter()
120            .flat_map(|l| l.into_iter())
121            .filter_map(|e| auth_param(e).ok())
122    }
123
124    /// The value of the parameter `name` (ASCII case-insensitive).
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use parse_net::{auth, AuthPayload, Limits};
130    ///
131    /// let c = auth::parse_credentials(b"Digest Realm=\"api\"", &Limits::DEFAULT)?;
132    /// let AuthPayload::Params(p) = c.payload() else { unreachable!() };
133    /// assert!(p.get("realm").is_some_and(|v| v.eq_bytes(b"api")));
134    /// assert!(p.get("nonce").is_none());
135    /// # Ok::<(), parse_net::ParseError>(())
136    /// ```
137    #[must_use]
138    pub fn get(&self, name: &str) -> Option<Word<'a>> {
139        self.iter()
140            .find(|(n, _)| n.eq_ignore_ascii_case(name.as_bytes()))
141            .map(|(_, v)| v)
142    }
143
144    /// The number of parameters.
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use parse_net::{auth, AuthPayload, Limits};
150    ///
151    /// let c = auth::parse_credentials(b"Digest a=1, b=2, c=3", &Limits::DEFAULT)?;
152    /// let AuthPayload::Params(p) = c.payload() else { unreachable!() };
153    /// assert_eq!(p.len(), 3);
154    /// # Ok::<(), parse_net::ParseError>(())
155    /// ```
156    #[must_use]
157    pub const fn len(&self) -> usize {
158        self.count
159    }
160
161    /// Whether there are no parameters.
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// use parse_net::{auth, AuthPayload, Limits};
167    ///
168    /// let c = auth::parse_credentials(b"Digest a=1", &Limits::DEFAULT)?;
169    /// let AuthPayload::Params(p) = c.payload() else { unreachable!() };
170    /// assert!(!p.is_empty());
171    /// # Ok::<(), parse_net::ParseError>(())
172    /// ```
173    #[must_use]
174    pub const fn is_empty(&self) -> bool {
175        self.count == 0
176    }
177}
178
179impl fmt::Debug for AuthParams<'_> {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        f.write_str("AuthParams(")?;
182        f.debug_list()
183            .entries(
184                self.iter()
185                    .map(|(n, _)| core::str::from_utf8(n).unwrap_or("?")),
186            )
187            .finish()?;
188        f.write_str(")")
189    }
190}
191
192/// What follows the scheme of a credential or a challenge.
193///
194/// # Examples
195///
196/// ```
197/// use parse_net::{auth, AuthPayload, Limits};
198///
199/// let l = Limits::DEFAULT;
200/// assert_eq!(auth::parse_credentials(b"Negotiate", &l)?.payload(), AuthPayload::None);
201/// assert_eq!(auth::parse_credentials(b"Bearer abc123", &l)?.payload(), AuthPayload::Token68(b"abc123"));
202/// assert!(matches!(auth::parse_credentials(b"Digest a=b", &l)?.payload(), AuthPayload::Params(_)));
203/// # Ok::<(), parse_net::ParseError>(())
204/// ```
205#[derive(Clone, Copy, PartialEq, Eq, Hash)]
206pub enum AuthPayload<'a> {
207    /// Nothing.
208    None,
209    /// A `token68`, such as a bearer token or `Basic` base64 text.
210    Token68(&'a [u8]),
211    /// `auth-param`s.
212    Params(AuthParams<'a>),
213}
214
215impl fmt::Debug for AuthPayload<'_> {
216    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217        match self {
218            AuthPayload::None => f.write_str("None"),
219            AuthPayload::Token68(_) => f.write_str("Token68(..)"),
220            AuthPayload::Params(p) => fmt::Debug::fmt(p, f),
221        }
222    }
223}
224
225/// Validated credentials (`Authorization`, `Proxy-Authorization`).
226///
227/// `Debug` prints the scheme only.
228///
229/// # Examples
230///
231/// ```
232/// use parse_net::{auth, AuthPayload, Limits};
233///
234/// let c = auth::parse_credentials(b"Bearer abc123", &Limits::DEFAULT)?;
235/// assert!(c.scheme_is("bearer"));
236/// assert_eq!(c.payload(), AuthPayload::Token68(b"abc123"));
237/// # Ok::<(), parse_net::ParseError>(())
238/// ```
239#[derive(Clone, Copy, PartialEq, Eq, Hash)]
240pub struct Credentials<'a> {
241    scheme: &'a [u8],
242    payload: AuthPayload<'a>,
243}
244
245impl<'a> Credentials<'a> {
246    /// The scheme, as sent.
247    ///
248    /// # Examples
249    ///
250    /// ```
251    /// use parse_net::{auth, Limits};
252    ///
253    /// let c = auth::parse_credentials(b"Bearer abc123", &Limits::DEFAULT)?;
254    /// assert_eq!(c.scheme(), b"Bearer");
255    /// # Ok::<(), parse_net::ParseError>(())
256    /// ```
257    #[must_use]
258    pub const fn scheme(&self) -> &'a [u8] {
259        self.scheme
260    }
261
262    /// Whether the scheme is `s` (ASCII case-insensitive).
263    ///
264    /// # Examples
265    ///
266    /// ```
267    /// use parse_net::{auth, Limits};
268    ///
269    /// let c = auth::parse_credentials(b"BASIC dXNlcjpwYXNz", &Limits::DEFAULT)?;
270    /// assert!(c.scheme_is("basic") && !c.scheme_is("bearer"));
271    /// # Ok::<(), parse_net::ParseError>(())
272    /// ```
273    #[must_use]
274    pub fn scheme_is(&self, s: &str) -> bool {
275        self.scheme.eq_ignore_ascii_case(s.as_bytes())
276    }
277
278    /// The payload.
279    ///
280    /// # Examples
281    ///
282    /// ```
283    /// use parse_net::{auth, AuthPayload, Limits};
284    ///
285    /// let c = auth::parse_credentials(b"Basic dXNlcjpwYXNz", &Limits::DEFAULT)?;
286    /// assert_eq!(c.payload(), AuthPayload::Token68(b"dXNlcjpwYXNz"));
287    /// # Ok::<(), parse_net::ParseError>(())
288    /// ```
289    #[must_use]
290    pub const fn payload(&self) -> AuthPayload<'a> {
291        self.payload
292    }
293}
294
295impl fmt::Debug for Credentials<'_> {
296    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297        f.debug_struct("Credentials")
298            .field("scheme", &core::str::from_utf8(self.scheme).unwrap_or("?"))
299            .finish_non_exhaustive()
300    }
301}
302
303/// Validates the `auth-param` list `text` (from `at` in the field) with no
304/// repeated name, and counts it.
305fn check_params(text: &[u8], at: usize, limits: &Limits) -> Result<usize> {
306    let l = list::parse(text, limits).map_err(|e| e.shifted(at))?;
307    let max = usize::from(limits.params).min(MAX_CHECKED_PARAMS);
308    let (mut count, mut names) = (0usize, Names::new());
309    for e in l.iter() {
310        let rel = (e.as_ptr() as usize).saturating_sub(text.as_ptr() as usize);
311        let off = at + rel;
312        let (name, _) = auth_param(e).map_err(|x| x.shifted(off))?;
313        count += 1;
314        if count > max {
315            return err(ErrorKind::TooManyMembers, off);
316        }
317        // A repeated name within one credential or challenge is refused
318        // (RFC 9110 section 11.2); the name starts the element.
319        if names.repeats(text, rel, name, class::is_tchar) {
320            return err(ErrorKind::DuplicateParameter, off);
321        }
322    }
323    Ok(count)
324}
325
326/// Parses `credentials = auth-scheme [ 1*SP ( token68 / #auth-param ) ]`.
327///
328/// One credential per field; parameter names may not repeat; OWS around
329/// `=` in a parameter is accepted (BWS).
330///
331/// # Errors
332///
333/// [`ErrorKind::NotToken`] for the scheme or a parameter name;
334/// [`ErrorKind::DuplicateParameter`]; [`ErrorKind::MissingValue`];
335/// [`ErrorKind::TrailingData`] (for example a second credential); the
336/// quoted-string and list errors.
337///
338/// # Examples
339///
340/// ```
341/// use parse_net::{auth, Limits};
342///
343/// assert!(auth::parse_credentials(b"Digest a=b, a=c", &Limits::DEFAULT).is_err());
344/// ```
345pub fn parse_credentials<'a>(value: &'a [u8], limits: &Limits) -> Result<Credentials<'a>> {
346    let lead = skip_ows(value);
347    let v = trim_ows(value);
348    let n = v.iter().take_while(|&&b| class::is_tchar(b)).count();
349    if n == 0 {
350        return Err(refused(v, 0, ErrorKind::NotToken).shifted(lead));
351    }
352    let scheme = v.get(..n).unwrap_or(&[]);
353    let rest = v.get(n..).unwrap_or(&[]);
354    if rest.is_empty() {
355        return Ok(Credentials {
356            scheme,
357            payload: AuthPayload::None,
358        });
359    }
360    let spaces = rest.iter().take_while(|&&b| b == b' ').count();
361    if spaces == 0 {
362        return Err(refused(v, n, ErrorKind::NotToken).shifted(lead));
363    }
364    let at = n + spaces;
365    let body = v.get(at..).unwrap_or(&[]);
366    if is_token68(body) {
367        return Ok(Credentials {
368            scheme,
369            payload: AuthPayload::Token68(body),
370        });
371    }
372    if !is_param_start(body) {
373        // A token68 followed by more, or a second scheme.
374        let t68 = body
375            .iter()
376            .take_while(|&&b| class::is_token68_char(b) || b == b'=')
377            .count();
378        return Err(refused(v, at + t68, ErrorKind::TrailingData).shifted(lead));
379    }
380    let count = check_params(body, lead + at, limits)?;
381    Ok(Credentials {
382        scheme,
383        payload: AuthPayload::Params(AuthParams { text: body, count }),
384    })
385}
386
387/// The `Bearer` token of an `Authorization` value (RFC 6750 section 2.1):
388/// `Some(token)` when the scheme is `Bearer` (case-insensitive), `None` for
389/// another scheme.
390///
391/// # Errors
392///
393/// [`ErrorKind::Empty`] for `Bearer` alone; [`ErrorKind::InvalidByte`] when
394/// the payload is not a `b64token`; [`ErrorKind::TrailingData`]; the scheme
395/// errors.
396///
397/// # Examples
398///
399/// ```
400/// assert_eq!(parse_net::bearer(b"Bearer abc123")?, Some(&b"abc123"[..]));
401/// assert_eq!(parse_net::bearer(b"Basic dXNlcjpwYXNz")?, None);
402/// assert!(parse_net::bearer(b"Bearer").is_err());
403/// # Ok::<(), parse_net::ParseError>(())
404/// ```
405pub fn bearer(value: &[u8]) -> Result<Option<&[u8]>> {
406    let c = parse_credentials(value, &Limits::DEFAULT)?;
407    if !c.scheme_is("bearer") {
408        return Ok(None);
409    }
410    match c.payload {
411        AuthPayload::Token68(t) => Ok(Some(t)),
412        AuthPayload::None => err(ErrorKind::Empty, value.len()),
413        AuthPayload::Params(p) => {
414            let at = (p.text.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
415            err(ErrorKind::InvalidByte, at)
416        }
417    }
418}
419
420/// The base64 text of a `Basic` `Authorization` value (RFC 7617): `Some`
421/// when the scheme is `Basic`, `None` for another scheme. The text is
422/// checked (the base64 alphabet, padding only at the end, a length that is a
423/// multiple of four) and not decoded.
424///
425/// # Errors
426///
427/// [`ErrorKind::InvalidBase64`]; [`ErrorKind::Empty`] for `Basic` alone;
428/// the scheme errors.
429///
430/// # Examples
431///
432/// ```
433/// assert_eq!(parse_net::basic(b"Basic dXNlcjpwYXNz")?, Some(&b"dXNlcjpwYXNz"[..]));
434/// assert_eq!(parse_net::basic(b"Bearer abc123")?, None);
435/// assert!(parse_net::basic(b"Basic YQ").is_err());
436/// # Ok::<(), parse_net::ParseError>(())
437/// ```
438pub fn basic(value: &[u8]) -> Result<Option<&[u8]>> {
439    let c = parse_credentials(value, &Limits::DEFAULT)?;
440    if !c.scheme_is("basic") {
441        return Ok(None);
442    }
443    let t = match c.payload {
444        AuthPayload::Token68(t) => t,
445        AuthPayload::None => return err(ErrorKind::Empty, value.len()),
446        AuthPayload::Params(p) => {
447            let at = (p.text.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
448            return err(ErrorKind::InvalidBase64, at);
449        }
450    };
451    let at = (t.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
452    let body = t.iter().take_while(|&&b| class::is_base64_char(b)).count();
453    let pad = t.len() - body;
454    if body < t.len() && t.get(body) != Some(&b'=') || pad > 2 || t.len() % 4 != 0 {
455        return err(ErrorKind::InvalidBase64, at + body);
456    }
457    Ok(Some(t))
458}
459
460/// One challenge.
461///
462/// # Examples
463///
464/// ```
465/// use parse_net::{auth, Limits};
466///
467/// let c = auth::parse_challenges(b"Bearer realm=\"api\", error=invalid_token", &Limits::DEFAULT)?;
468/// let first = c.iter().next().expect("one challenge");
469/// assert!(first.scheme_is("bearer"));
470/// assert!(first.param("error").is_some_and(|v| v.eq_bytes(b"invalid_token")));
471/// # Ok::<(), parse_net::ParseError>(())
472/// ```
473#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
474pub struct Challenge<'a> {
475    scheme: &'a [u8],
476    payload: AuthPayload<'a>,
477}
478
479impl<'a> Challenge<'a> {
480    /// The scheme, as sent.
481    ///
482    /// # Examples
483    ///
484    /// ```
485    /// use parse_net::{auth, Limits};
486    ///
487    /// let c = auth::parse_challenges(b"Basic realm=\"x\", Negotiate", &Limits::DEFAULT)?;
488    /// let schemes: Vec<&[u8]> = c.iter().map(|x| x.scheme()).collect();
489    /// assert_eq!(schemes, [&b"Basic"[..], b"Negotiate"]);
490    /// # Ok::<(), parse_net::ParseError>(())
491    /// ```
492    #[must_use]
493    pub const fn scheme(&self) -> &'a [u8] {
494        self.scheme
495    }
496
497    /// Whether the scheme is `s` (ASCII case-insensitive).
498    ///
499    /// # Examples
500    ///
501    /// ```
502    /// use parse_net::{auth, Limits};
503    ///
504    /// let c = auth::parse_challenges(b"NEGOTIATE", &Limits::DEFAULT)?;
505    /// assert!(c.iter().next().is_some_and(|x| x.scheme_is("negotiate")));
506    /// # Ok::<(), parse_net::ParseError>(())
507    /// ```
508    #[must_use]
509    pub fn scheme_is(&self, s: &str) -> bool {
510        self.scheme.eq_ignore_ascii_case(s.as_bytes())
511    }
512
513    /// The payload.
514    ///
515    /// # Examples
516    ///
517    /// ```
518    /// use parse_net::{auth, AuthPayload, Limits};
519    ///
520    /// let c = auth::parse_challenges(b"Negotiate abc==", &Limits::DEFAULT)?;
521    /// let first = c.iter().next().expect("one challenge");
522    /// assert_eq!(first.payload(), AuthPayload::Token68(b"abc=="));
523    /// # Ok::<(), parse_net::ParseError>(())
524    /// ```
525    #[must_use]
526    pub const fn payload(&self) -> AuthPayload<'a> {
527        self.payload
528    }
529
530    /// The parameter `name` (`realm`, `error`, `scope`, ...).
531    ///
532    /// # Examples
533    ///
534    /// ```
535    /// use parse_net::{auth, Limits};
536    ///
537    /// let c = auth::parse_challenges(b"Basic realm=\"simple\", charset=UTF-8", &Limits::DEFAULT)?;
538    /// let first = c.iter().next().expect("one challenge");
539    /// assert!(first.param("Realm").is_some_and(|v| v.eq_bytes(b"simple")));
540    /// assert!(first.param("scope").is_none());
541    /// # Ok::<(), parse_net::ParseError>(())
542    /// ```
543    #[must_use]
544    pub fn param(&self, name: &str) -> Option<Word<'a>> {
545        match self.payload {
546            AuthPayload::Params(p) => p.get(name),
547            _ => None,
548        }
549    }
550}
551
552/// Validated challenges.
553///
554/// # Examples
555///
556/// ```
557/// use parse_net::{auth, Limits};
558///
559/// let c = auth::parse_challenges(b"Basic realm=\"a\", Bearer", &Limits::DEFAULT)?;
560/// assert_eq!(c.len(), 2);
561/// assert!(c.iter().any(|x| x.scheme_is("bearer")));
562/// # Ok::<(), parse_net::ParseError>(())
563/// ```
564#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
565pub struct Challenges<'a> {
566    list: list::List<'a>,
567    value: &'a [u8],
568    count: usize,
569}
570
571impl<'a> Challenges<'a> {
572    /// The challenges, in order.
573    ///
574    /// # Examples
575    ///
576    /// ```
577    /// use parse_net::{auth, Limits};
578    ///
579    /// let c = auth::parse_challenges(b"Basic realm=\"a\", Bearer", &Limits::DEFAULT)?;
580    /// let mut it = c.iter();
581    /// assert!(it.next().is_some_and(|x| x.scheme_is("basic")));
582    /// assert!(it.next().is_some_and(|x| x.scheme_is("bearer")));
583    /// assert!(it.next().is_none());
584    /// # Ok::<(), parse_net::ParseError>(())
585    /// ```
586    #[must_use]
587    pub fn iter(&self) -> ChallengeIter<'a> {
588        ChallengeIter {
589            value: self.value,
590            elements: self.list.iter().peekable(),
591        }
592    }
593
594    /// The number of challenges.
595    ///
596    /// # Examples
597    ///
598    /// ```
599    /// use parse_net::{auth, Limits};
600    ///
601    /// let c = auth::parse_challenges(b"A, B realm=x, C abc==", &Limits::DEFAULT)?;
602    /// assert_eq!(c.len(), 3);
603    /// # Ok::<(), parse_net::ParseError>(())
604    /// ```
605    #[must_use]
606    pub const fn len(&self) -> usize {
607        self.count
608    }
609
610    /// Whether there are no challenges.
611    ///
612    /// # Examples
613    ///
614    /// ```
615    /// use parse_net::{auth, Limits};
616    ///
617    /// assert!(auth::parse_challenges(b"", &Limits::DEFAULT)?.is_empty());
618    /// assert!(!auth::parse_challenges(b"Basic", &Limits::DEFAULT)?.is_empty());
619    /// # Ok::<(), parse_net::ParseError>(())
620    /// ```
621    #[must_use]
622    pub const fn is_empty(&self) -> bool {
623        self.count == 0
624    }
625}
626
627/// The challenges of a validated value.
628///
629/// # Examples
630///
631/// ```
632/// use parse_net::{auth::{self, ChallengeIter}, Limits};
633///
634/// let c = auth::parse_challenges(b"Negotiate, Basic realm=\"x\"", &Limits::DEFAULT)?;
635/// let it: ChallengeIter<'_> = c.iter();
636/// assert_eq!(it.count(), 2);
637/// # Ok::<(), parse_net::ParseError>(())
638/// ```
639#[derive(Clone, Debug)]
640pub struct ChallengeIter<'a> {
641    value: &'a [u8],
642    elements: Peekable<list::Elements<'a>>,
643}
644
645/// The start of challenge element `e`: its scheme and what follows.
646fn challenge_start(e: &[u8]) -> (&[u8], &[u8]) {
647    let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
648    let scheme = e.get(..n).unwrap_or(&[]);
649    let rest = e.get(n..).unwrap_or(&[]);
650    let rest = rest
651        .get(rest.iter().take_while(|&&b| b == b' ').count()..)
652        .unwrap_or(&[]);
653    (scheme, rest)
654}
655
656impl<'a> Iterator for ChallengeIter<'a> {
657    type Item = Challenge<'a>;
658
659    fn next(&mut self) -> Option<Challenge<'a>> {
660        let e = self.elements.next()?;
661        let (scheme, rest) = challenge_start(e);
662        if rest.is_empty() {
663            return Some(Challenge {
664                scheme,
665                payload: AuthPayload::None,
666            });
667        }
668        if is_token68(rest) {
669            return Some(Challenge {
670                scheme,
671                payload: AuthPayload::Token68(rest),
672            });
673        }
674        // The first parameter is in this element; the rest follow it as
675        // separate elements until the next challenge starts.
676        let start = (rest.as_ptr() as usize).saturating_sub(self.value.as_ptr() as usize);
677        let mut end = start + rest.len();
678        let mut count = 1usize;
679        while let Some(next) = self.elements.peek() {
680            if !is_param_start(next) {
681                break;
682            }
683            end =
684                (next.as_ptr() as usize).saturating_sub(self.value.as_ptr() as usize) + next.len();
685            count += 1;
686            let _skipped = self.elements.next();
687        }
688        let text = self.value.get(start..end).unwrap_or(&[]);
689        Some(Challenge {
690            scheme,
691            payload: AuthPayload::Params(AuthParams { text, count }),
692        })
693    }
694}
695
696/// Parses `WWW-Authenticate` or `Proxy-Authenticate`: `#challenge`.
697///
698/// A challenge's own parameters are comma-separated too, so where one
699/// challenge ends is decided element by element: a token followed by `=` is
700/// a parameter of the current challenge; a token alone, or a token and
701/// `1*SP` before a `token68` or a parameter, starts a new one. A parameter
702/// name may not repeat within one challenge (RFC 9110 section 11.2).
703///
704/// # Errors
705///
706/// [`ErrorKind::DuplicateParameter`]; [`ErrorKind::NotToken`];
707/// [`ErrorKind::ExpectedSeparator`] for a parameter before any challenge;
708/// [`ErrorKind::TrailingData`]; the list and quoted-string errors.
709///
710/// # Examples
711///
712/// ```
713/// use parse_net::{auth, Limits};
714///
715/// let c = auth::parse_challenges(
716///     b"Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"",
717///     &Limits::DEFAULT)?;
718/// let all: Vec<_> = c.iter().collect();
719/// assert_eq!(all.len(), 2);
720/// assert!(all[0].param("type").is_some_and(|v| v.eq_bytes(b"1")));
721/// assert!(all[1].scheme_is("basic"));
722/// # Ok::<(), parse_net::ParseError>(())
723/// ```
724pub fn parse_challenges<'a>(value: &'a [u8], limits: &Limits) -> Result<Challenges<'a>> {
725    let l = list::parse(value, limits)?;
726    let mut count = 0usize;
727    let mut elements = l.iter().peekable();
728    while let Some(e) = elements.next() {
729        let at = (e.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
730        if is_param_start(e) {
731            return err(ErrorKind::ExpectedSeparator, at);
732        }
733        let n = e.iter().take_while(|&&b| class::is_tchar(b)).count();
734        if n == 0 {
735            return Err(refused(e, 0, ErrorKind::NotToken).shifted(at));
736        }
737        let (_, rest) = challenge_start(e);
738        if e.get(n).is_some_and(|&b| b != b' ') {
739            return Err(refused(e, n, ErrorKind::NotToken).shifted(at));
740        }
741        count += 1;
742        if rest.is_empty() || is_token68(rest) {
743            continue;
744        }
745        if !is_param_start(rest) {
746            let off = e.len() - rest.len();
747            return Err(refused(e, off, ErrorKind::TrailingData).shifted(at));
748        }
749        // This challenge's parameters: the rest of this element and every
750        // following parameter element.
751        let start = at + (e.len() - rest.len());
752        let mut end = at + e.len();
753        while let Some(next) = elements.peek() {
754            if !is_param_start(next) {
755                break;
756            }
757            end = (next.as_ptr() as usize).saturating_sub(value.as_ptr() as usize) + next.len();
758            let _skipped = elements.next();
759        }
760        let _params = check_params(value.get(start..end).unwrap_or(&[]), start, limits)?;
761    }
762    Ok(Challenges {
763        list: l,
764        value,
765        count,
766    })
767}
768
769fn put_challenge(
770    scheme: &str,
771    params: &[(&str, &str)],
772    out: &mut impl Sink,
773) -> core::result::Result<(), WriteError> {
774    out.put(scheme.as_bytes())?;
775    for (i, (name, value)) in params.iter().enumerate() {
776        out.put(if i == 0 { b" " } else { b", " })?;
777        out.put(name.as_bytes())?;
778        out.put(b"=")?;
779        if name.eq_ignore_ascii_case("realm") {
780            write_quoted(value.as_bytes(), out)?;
781        } else {
782            write_word(value.as_bytes(), out)?;
783        }
784    }
785    Ok(())
786}
787
788/// Writes a challenge with parameters: `Bearer realm="api", error="invalid_token"`.
789/// `realm` is always quoted (RFC 9110 section 11.5); other values are tokens
790/// when they can be.
791///
792/// # Errors
793///
794/// [`WriteError::NotToken`] for the scheme or a name;
795/// [`WriteError::DuplicateName`] for a name given twice (RFC 9110 section
796/// 11.2 allows each once); [`WriteError::Forbidden`] for a control byte in a
797/// value; [`WriteError::Full`] (nothing written when the sink reports its
798/// room).
799///
800/// # Examples
801///
802/// ```
803/// use parse_net::{auth, SliceSink};
804///
805/// let mut buf = [0u8; 64];
806/// let mut out = SliceSink::new(&mut buf);
807/// auth::write_challenge("Bearer", &[("realm", "api"), ("error", "invalid_token")], &mut out).expect("room");
808/// assert_eq!(out.written(), b"Bearer realm=\"api\", error=invalid_token");
809/// ```
810pub fn write_challenge(
811    scheme: &str,
812    params: &[(&str, &str)],
813    out: &mut impl Sink,
814) -> core::result::Result<(), WriteError> {
815    if !is_token(scheme.as_bytes()) || params.iter().any(|(n, _)| !is_token(n.as_bytes())) {
816        return Err(WriteError::NotToken);
817    }
818    unique_names(params)?;
819    let mut count = CountSink::new();
820    put_challenge(scheme, params, &mut count)?;
821    reserve(out, count.count())?;
822    put_challenge(scheme, params, out)
823}
824
825/// Writes a challenge with a `token68`: `Negotiate <token68>`.
826///
827/// # Errors
828///
829/// [`WriteError::NotToken`] for the scheme; [`WriteError::Forbidden`] when
830/// `token68` is not a `token68`; [`WriteError::Full`].
831///
832/// # Examples
833///
834/// ```
835/// use parse_net::{auth, SliceSink};
836///
837/// let mut buf = [0u8; 32];
838/// let mut out = SliceSink::new(&mut buf);
839/// auth::write_challenge_token68("Negotiate", b"YIIB==", &mut out).expect("room");
840/// assert_eq!(out.written(), b"Negotiate YIIB==");
841/// ```
842pub fn write_challenge_token68(
843    scheme: &str,
844    token68: &[u8],
845    out: &mut impl Sink,
846) -> core::result::Result<(), WriteError> {
847    if !is_token(scheme.as_bytes()) {
848        return Err(WriteError::NotToken);
849    }
850    if !is_token68(token68) {
851        let at = token68
852            .iter()
853            .position(|&b| !(class::is_token68_char(b) || b == b'='))
854            .unwrap_or(0);
855        return Err(WriteError::forbidden(at));
856    }
857    reserve(out, scheme.len() + 1 + token68.len())?;
858    out.put(scheme.as_bytes())?;
859    out.put(b" ")?;
860    out.put(token68)?;
861    Ok(())
862}
863
864#[cfg(test)]
865mod tests {
866    use std::{format, vec::Vec};
867
868    use super::*;
869
870    #[test]
871    fn test_bearer_cases() {
872        assert_eq!(bearer(b"Bearer abc").unwrap(), Some(&b"abc"[..]));
873        assert_eq!(bearer(b"bearer abc").unwrap(), Some(&b"abc"[..]));
874        assert_eq!(bearer(b"Bearer  abc").unwrap(), Some(&b"abc"[..]));
875        assert_eq!(bearer(b"Bearer abc==").unwrap(), Some(&b"abc=="[..]));
876        assert_eq!(bearer(b"Bearer").unwrap_err().kind(), ErrorKind::Empty);
877        assert_eq!(
878            bearer(b"Bearer abc def").unwrap_err().kind(),
879            ErrorKind::TrailingData
880        );
881        assert_eq!(
882            bearer(b"Bearer a=b").unwrap_err().kind(),
883            ErrorKind::InvalidByte
884        );
885        assert_eq!(
886            bearer(b"Bearer\tabc").unwrap_err().kind(),
887            ErrorKind::NotToken
888        );
889        assert_eq!(bearer(b"").unwrap_err().kind(), ErrorKind::NotToken);
890        assert_eq!(bearer(b"Digest a=b").unwrap(), None);
891    }
892
893    #[test]
894    fn test_basic_cases() {
895        assert_eq!(
896            basic(b"Basic dXNlcjpwYXNz").unwrap(),
897            Some(&b"dXNlcjpwYXNz"[..])
898        );
899        assert_eq!(basic(b"Basic YQ==").unwrap(), Some(&b"YQ=="[..]));
900        assert_eq!(basic(b"Basic").unwrap_err().kind(), ErrorKind::Empty);
901        assert_eq!(
902            basic(b"Basic YQ").unwrap_err().kind(),
903            ErrorKind::InvalidBase64
904        );
905        assert_eq!(
906            basic(b"Basic a-b_").unwrap_err().kind(),
907            ErrorKind::InvalidBase64
908        );
909        assert_eq!(
910            basic(b"Basic YQ===").unwrap_err().kind(),
911            ErrorKind::InvalidBase64
912        );
913        assert_eq!(basic(b"Bearer x").unwrap(), None);
914    }
915
916    #[test]
917    fn test_credentials_params_and_duplicates() {
918        let l = Limits::DEFAULT;
919        let c = parse_credentials(b"Digest username = \"a\" , realm=r, ", &l).unwrap();
920        let AuthPayload::Params(p) = c.payload() else {
921            panic!("params")
922        };
923        assert_eq!(p.len(), 2);
924        assert!(p.get("USERNAME").unwrap().eq_bytes(b"a"));
925        assert_eq!(
926            parse_credentials(b"Digest a=b, A=c", &l)
927                .unwrap_err()
928                .kind(),
929            ErrorKind::DuplicateParameter
930        );
931        assert_eq!(
932            parse_credentials(b"Negotiate token68===", &l)
933                .unwrap()
934                .payload(),
935            AuthPayload::Token68(b"token68===")
936        );
937        assert_eq!(
938            parse_credentials(b"Digest a", &l).unwrap().payload(),
939            AuthPayload::Token68(b"a")
940        );
941        assert_eq!(
942            parse_credentials(b"Digest a b", &l).unwrap_err().kind(),
943            ErrorKind::TrailingData
944        );
945        // `a=` is a token68 with padding; `a=, b` is a parameter with no value.
946        assert_eq!(
947            parse_credentials(b"Digest a=", &l).unwrap().payload(),
948            AuthPayload::Token68(b"a=")
949        );
950        assert_eq!(
951            parse_credentials(b"Digest a=, b=1", &l).unwrap_err().kind(),
952            ErrorKind::MissingValue
953        );
954        assert_eq!(
955            parse_credentials(b"Digest a=\"x", &l).unwrap_err().kind(),
956            ErrorKind::UnterminatedQuote
957        );
958    }
959
960    #[test]
961    fn test_credentials_debug_redacted() {
962        let c = parse_credentials(b"Bearer s3cr3t-token", &Limits::DEFAULT).unwrap();
963        let d = format!("{c:?} {:?}", c.payload());
964        assert!(!d.contains("s3cr3t"));
965        let p = parse_credentials(b"Digest response=\"s3cr3t\"", &Limits::DEFAULT).unwrap();
966        let d = format!("{:?}", p.payload());
967        assert_eq!(d, "AuthParams([\"response\"])");
968    }
969
970    #[test]
971    fn test_challenges_rfc_examples() {
972        let l = Limits::DEFAULT;
973        let names = |v: &[u8]| -> Vec<Vec<u8>> {
974            parse_challenges(v, &l)
975                .unwrap()
976                .iter()
977                .map(|c| c.scheme().to_vec())
978                .collect()
979        };
980        assert_eq!(
981            names(b"Basic realm=\"a\", Bearer"),
982            [b"Basic".to_vec(), b"Bearer".to_vec()]
983        );
984        let c = parse_challenges(b"Bearer realm=\"a\", error=\"invalid_token\"", &l).unwrap();
985        let one: Vec<_> = c.iter().collect();
986        assert_eq!(one.len(), 1);
987        assert!(one[0].param("error").unwrap().eq_bytes(b"invalid_token"));
988        assert_eq!(
989            names(b"Negotiate abc==, Basic realm=x"),
990            [b"Negotiate".to_vec(), b"Basic".to_vec()]
991        );
992        assert_eq!(
993            names(b"A, , B, ,C"),
994            [b"A".to_vec(), b"B".to_vec(), b"C".to_vec()]
995        );
996        let t68 = parse_challenges(b"Negotiate abc==", &l).unwrap();
997        assert_eq!(
998            t68.iter().next().unwrap().payload(),
999            AuthPayload::Token68(b"abc==")
1000        );
1001    }
1002
1003    #[test]
1004    fn test_challenge_refusals() {
1005        let l = Limits::DEFAULT;
1006        let k = |v: &[u8]| parse_challenges(v, &l).unwrap_err().kind();
1007        assert_eq!(k(b"Basic realm=a, realm=b"), ErrorKind::DuplicateParameter);
1008        assert_eq!(k(b"realm=a"), ErrorKind::ExpectedSeparator);
1009        assert_eq!(k(b"Basic\trealm=a"), ErrorKind::NotToken);
1010        assert_eq!(k(b"Basic abc def"), ErrorKind::TrailingData);
1011        assert!(parse_challenges(b"Basic realm=a, Digest realm=b", &l).is_ok());
1012    }
1013
1014    #[test]
1015    fn test_challenge_writers_round_trip() {
1016        let mut buf = [0u8; 128];
1017        let mut out = crate::SliceSink::new(&mut buf);
1018        write_challenge(
1019            "Bearer",
1020            &[("realm", "api"), ("scope", "read write")],
1021            &mut out,
1022        )
1023        .unwrap();
1024        let c = parse_challenges(out.written(), &Limits::DEFAULT).unwrap();
1025        let first = c.iter().next().unwrap();
1026        assert!(first.param("realm").unwrap().eq_bytes(b"api"));
1027        assert!(first.param("scope").unwrap().eq_bytes(b"read write"));
1028        assert_eq!(
1029            write_challenge("Bad scheme", &[], &mut out),
1030            Err(WriteError::NotToken)
1031        );
1032        assert_eq!(
1033            write_challenge("Basic", &[("realm", "a"), ("Realm", "b")], &mut out),
1034            Err(WriteError::DuplicateName)
1035        );
1036        assert_eq!(
1037            write_challenge("Basic", &[("realm", "a\r\n")], &mut out),
1038            Err(WriteError::Forbidden { at: 1 })
1039        );
1040        assert_eq!(
1041            write_challenge_token68("Negotiate", b"a b", &mut out),
1042            Err(WriteError::Forbidden { at: 1 })
1043        );
1044    }
1045}