ttpkit_auth/
challenge.rs

1//! Authentication challenge.
2
3use str_reader::StringReader;
4use ttpkit::header::HeaderFieldValue;
5
6use crate::{Error, StringReaderExt as _};
7
8/// Authentication challenge.
9#[derive(Debug, Clone)]
10pub struct AuthChallenge {
11    scheme: String,
12    token: Option<String>,
13    params: Vec<ChallengeParam>,
14}
15
16impl AuthChallenge {
17    /// Parse authentication challenges from a given header value.
18    pub fn parse(header: &HeaderFieldValue) -> Result<Vec<Self>, Error> {
19        let challenge = header
20            .to_str()
21            .map_err(|_| Error::from_static_msg("authentication challenge is not UTF-8 encoded"))?;
22
23        let mut res = Vec::new();
24
25        let mut reader = StringReader::new(challenge);
26
27        while !reader.is_empty() {
28            if let Some(challenge) = reader.parse_challenge()? {
29                res.push(challenge);
30            }
31
32            reader.skip_whitespace();
33
34            // skip all possible empty list items
35            while reader.current_char() == Some(',') {
36                reader.skip_char();
37                reader.skip_whitespace();
38            }
39        }
40
41        Ok(res)
42    }
43
44    /// Get the authentication scheme.
45    ///
46    /// The scheme is always in lowercase.
47    #[inline]
48    pub fn scheme(&self) -> &str {
49        &self.scheme
50    }
51
52    /// Get the optional challenge token.
53    #[inline]
54    pub fn token(&self) -> Option<&str> {
55        self.token.as_deref()
56    }
57
58    /// Get the challenge parameters.
59    #[inline]
60    pub fn params(&self) -> &[ChallengeParam] {
61        &self.params
62    }
63}
64
65/// Challenge parameter.
66#[derive(Debug, Clone)]
67pub struct ChallengeParam {
68    name: String,
69    value: String,
70}
71
72impl ChallengeParam {
73    /// Get the parameter name.
74    ///
75    /// The name is always in lowercase.
76    #[inline]
77    pub fn name(&self) -> &str {
78        &self.name
79    }
80
81    /// Get the parameter value.
82    #[inline]
83    pub fn value(&self) -> &str {
84        &self.value
85    }
86}
87
88/// Helper trait.
89trait StringReaderExt {
90    /// Parse an authentication challenge.
91    fn parse_challenge(&mut self) -> Result<Option<AuthChallenge>, Error>;
92
93    /// Parse a challenge token.
94    fn parse_challenge_token(&mut self) -> Option<String>;
95
96    /// Parse a challenge parameter.
97    fn parse_challenge_param(&mut self) -> Result<Option<ChallengeParam>, Error>;
98}
99
100impl StringReaderExt for StringReader<'_> {
101    fn parse_challenge(&mut self) -> Result<Option<AuthChallenge>, Error> {
102        let mut reader = StringReader::new(self.as_str());
103
104        reader.skip_whitespace();
105
106        let name = reader.read_until(|c| c == ',' || c.is_whitespace());
107
108        if name.is_empty() {
109            // advance the original reader position before returning
110            *self = reader;
111
112            return Ok(None);
113        }
114
115        let mut challenge = AuthChallenge {
116            scheme: name.to_ascii_lowercase(),
117            token: None,
118            params: Vec::new(),
119        };
120
121        if let Some(token) = reader.parse_challenge_token() {
122            challenge.token = Some(token);
123        } else {
124            while let Some(param) = reader.parse_challenge_param()? {
125                challenge.params.push(param);
126
127                reader.skip_whitespace();
128
129                // skip the rest of the parameter (in case of a malformed input)
130                if let Some(c) = reader.current_char()
131                    && c != ','
132                {
133                    reader.read_until(|c| c == ',');
134                }
135
136                reader.skip_char();
137            }
138        }
139
140        *self = reader;
141
142        Ok(Some(challenge))
143    }
144
145    fn parse_challenge_token(&mut self) -> Option<String> {
146        let mut reader = StringReader::new(self.as_str());
147
148        reader.skip_whitespace();
149
150        let token = reader.read_until(|c| c == ',' || c.is_whitespace());
151
152        // check if it's a parameter
153        if token.trim_end_matches('=').contains('=') || token.is_empty() {
154            // NOTE: We don't advance the original reader position in this case
155            //   because this means that the input contains no challenge token.
156            //   However, it may still contain challenge parameters or another
157            //   challenge, so we need to leave the reader position unchanged
158            //   for the outer parser.
159            return None;
160        }
161
162        *self = reader;
163
164        Some(token.into())
165    }
166
167    fn parse_challenge_param(&mut self) -> Result<Option<ChallengeParam>, Error> {
168        let mut reader = StringReader::new(self.as_str());
169
170        reader.skip_whitespace();
171
172        let name = reader.read_until(|c| c == '=' || c == ',' || c.is_whitespace());
173
174        reader.skip_whitespace();
175
176        if reader.match_char('=').is_err() {
177            // NOTE: We don't advance the original reader position in this case
178            //   because this means that the input contains no more parameters.
179            //   However, it may contain another challenge, so we need to leave
180            //   the reader position unchanged for the outer parser.
181            return Ok(None);
182        } else if name.is_empty() {
183            return Err(Error::from_static_msg("empty challenge parameter name"));
184        }
185
186        reader.skip_whitespace();
187
188        let value = if reader.current_char() == Some('"') {
189            reader.parse_quoted_string()?
190        } else {
191            reader.read_until(|c| c == ',' || c.is_whitespace()).into()
192        };
193
194        let res = ChallengeParam {
195            name: name.to_ascii_lowercase(),
196            value,
197        };
198
199        *self = reader;
200
201        Ok(Some(res))
202    }
203}