1use str_reader::StringReader;
4use ttpkit::header::HeaderFieldValue;
5
6use crate::{Error, StringReaderExt as _};
7
8#[derive(Debug, Clone)]
10pub struct AuthChallenge {
11 scheme: String,
12 token: Option<String>,
13 params: Vec<ChallengeParam>,
14}
15
16impl AuthChallenge {
17 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 while reader.current_char() == Some(',') {
36 reader.skip_char();
37 reader.skip_whitespace();
38 }
39 }
40
41 Ok(res)
42 }
43
44 #[inline]
48 pub fn scheme(&self) -> &str {
49 &self.scheme
50 }
51
52 #[inline]
54 pub fn token(&self) -> Option<&str> {
55 self.token.as_deref()
56 }
57
58 #[inline]
60 pub fn params(&self) -> &[ChallengeParam] {
61 &self.params
62 }
63}
64
65#[derive(Debug, Clone)]
67pub struct ChallengeParam {
68 name: String,
69 value: String,
70}
71
72impl ChallengeParam {
73 #[inline]
77 pub fn name(&self) -> &str {
78 &self.name
79 }
80
81 #[inline]
83 pub fn value(&self) -> &str {
84 &self.value
85 }
86}
87
88trait StringReaderExt {
90 fn parse_challenge(&mut self) -> Result<Option<AuthChallenge>, Error>;
92
93 fn parse_challenge_token(&mut self) -> Option<String>;
95
96 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 *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 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 if token.trim_end_matches('=').contains('=') || token.is_empty() {
154 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 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}