1use alloc::{
2 format,
3 string::{String, ToString as _},
4 vec,
5 vec::Vec,
6};
7use core::{
8 ops::Deref,
9 str::{self, FromStr},
10};
11
12use http_auth::ChallengeParser;
13
14use crate::{
15 challenge::Challenge,
16 schemes::{NAME_BASIC, NAME_BEARER, NAME_DIGEST},
17 COMMA, SP,
18};
19
20#[derive(Debug, Clone)]
22pub struct Challenges(pub Vec<Challenge>);
23
24impl Deref for Challenges {
25 type Target = Vec<Challenge>;
26
27 fn deref(&self) -> &Self::Target {
28 &self.0
29 }
30}
31
32impl Challenges {
33 pub fn new(inner: Vec<Challenge>) -> Self {
34 Self(inner)
35 }
36
37 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, ChallengesParseError> {
38 let bytes = bytes.as_ref();
39 let s = str::from_utf8(bytes).map_err(ChallengesParseError::ChallengesToStrFailed)?;
40 Self::internal_from_str(s)
41 }
42
43 fn internal_from_str(s: impl AsRef<str>) -> Result<Self, ChallengesParseError> {
44 let s = s.as_ref();
45
46 if s.is_empty() {
47 return Err(ChallengesParseError::Other("empty"));
48 }
49
50 let challenge_parser = ChallengeParser::new(s);
51
52 #[allow(unused_mut)]
53 let mut inner = vec![];
54 for c in challenge_parser {
55 let c = c.map_err(|err| ChallengesParseError::ChallengeParserError(err.to_string()))?;
56 match c.scheme {
57 x if x.eq_ignore_ascii_case(NAME_BASIC) => {
58 #[cfg(feature = "scheme-basic")]
59 {
60 let c = crate::schemes::basic::Challenge::try_from(&c)
61 .map(Challenge::Basic)
62 .map_err(ChallengesParseError::Basic)?;
63
64 inner.push(c)
65 }
66 #[cfg(not(feature = "scheme-basic"))]
67 {
68 return Err(ChallengesParseError::SchemeUnsupported(
69 "Require feature scheme-basic",
70 ));
71 }
72 }
73 x if x.eq_ignore_ascii_case(NAME_BEARER) => {
74 #[cfg(feature = "scheme-bearer")]
75 {
76 let c = crate::schemes::bearer::Challenge::try_from(&c)
77 .map(Challenge::Bearer)
78 .map_err(ChallengesParseError::Bearer)?;
79
80 inner.push(c)
81 }
82 #[cfg(not(feature = "scheme-bearer"))]
83 {
84 return Err(ChallengesParseError::SchemeUnsupported(
85 "Require feature scheme-bearer",
86 ));
87 }
88 }
89 x if x.eq_ignore_ascii_case(NAME_DIGEST) => {
90 return Err(ChallengesParseError::SchemeUnsupported("Unimplemented"))
91 }
92 _ => return Err(ChallengesParseError::SchemeUnknown),
93 }
94 }
95 Ok(Self::new(inner))
96 }
97}
98
99#[derive(Debug)]
101pub enum ChallengesParseError {
102 ChallengesToStrFailed(str::Utf8Error),
103 ChallengeParserError(String),
104 #[cfg(feature = "scheme-basic")]
105 Basic(crate::schemes::basic::ChallengeParseError),
106 #[cfg(feature = "scheme-bearer")]
107 Bearer(crate::schemes::bearer::ChallengeParseError),
108 SchemeUnknown,
109 SchemeUnsupported(&'static str),
110 Other(&'static str),
111}
112
113impl core::fmt::Display for ChallengesParseError {
114 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
115 write!(f, "{self:?}")
116 }
117}
118
119#[cfg(feature = "std")]
120impl std::error::Error for ChallengesParseError {}
121
122impl core::fmt::Display for Challenges {
123 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
124 write!(f, "{}", ChallengesWithSlice(self.0.as_ref()))
125 }
126}
127
128impl FromStr for Challenges {
130 type Err = ChallengesParseError;
131
132 fn from_str(s: &str) -> Result<Self, Self::Err> {
133 Self::internal_from_str(s)
134 }
135}
136
137#[derive(Debug, Clone)]
140pub(crate) struct ChallengesWithSlice<'a>(&'a [Challenge]);
141
142impl<'a> ChallengesWithSlice<'a> {
143 #[allow(dead_code)]
144 pub(crate) fn new(inner: &'a [Challenge]) -> Self {
145 Self(inner)
146 }
147}
148
149impl core::fmt::Display for ChallengesWithSlice<'_> {
151 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
152 write!(
153 f,
154 "{}",
155 self.0
156 .iter()
157 .map(|x| x.to_string())
158 .collect::<Vec<_>>()
159 .join(format!("{COMMA}{SP}").as_str())
160 )
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_parse_and_render() {
170 #[cfg(feature = "scheme-basic")]
172 {
173 use crate::schemes::basic::{
174 DEMO_CHALLENGE_CHARSET_STR, DEMO_CHALLENGE_REALM_STR, DEMO_CHALLENGE_STR,
175 DEMO_CHALLENGE_STR_SIMPLE,
176 };
177
178 match DEMO_CHALLENGE_STR.parse::<Challenges>() {
179 Ok(c) => {
180 assert_eq!(c.0.len(), 1);
181 let c = c.0.first().unwrap();
182 let c = c.as_basic().unwrap();
183 assert_eq!(c.realm, DEMO_CHALLENGE_REALM_STR.into());
184 assert_eq!(c.charset, Some(DEMO_CHALLENGE_CHARSET_STR.into()));
185 assert_eq!(c.to_string(), DEMO_CHALLENGE_STR);
186 }
187 x => panic!("{x:?}"),
188 }
189
190 match DEMO_CHALLENGE_STR_SIMPLE.parse::<Challenges>() {
191 Ok(c) => {
192 assert_eq!(c.0.len(), 1);
193 let c = c.0.first().unwrap();
194 let c = c.as_basic().unwrap();
195 assert_eq!(c.realm, DEMO_CHALLENGE_REALM_STR.into());
196 assert_eq!(c.charset, None);
197 assert_eq!(c.to_string(), DEMO_CHALLENGE_STR_SIMPLE);
198 }
199 x => panic!("{x:?}"),
200 }
201 }
202 #[cfg(not(feature = "scheme-basic"))]
203 {
204 match "Basic".parse::<Challenges>() {
205 Err(ChallengesParseError::SchemeUnsupported(_)) => {}
206 x => panic!("{x:?}"),
207 }
208 }
209
210 #[cfg(feature = "scheme-bearer")]
212 {
213 use crate::schemes::bearer::{
214 DEMO_CHALLENGE_ERROR_DESCRIPTION_STR, DEMO_CHALLENGE_ERROR_STR,
215 DEMO_CHALLENGE_REALM_STR, DEMO_CHALLENGE_STR, DEMO_CHALLENGE_STR_SIMPLE,
216 };
217
218 match DEMO_CHALLENGE_STR.parse::<Challenges>() {
219 Ok(c) => {
220 assert_eq!(c.0.len(), 1);
221 let c = c.0.first().unwrap();
222 let c = c.as_bearer().unwrap();
223 assert_eq!(c.realm, DEMO_CHALLENGE_REALM_STR.into());
224 assert_eq!(c.scope, None);
225 assert_eq!(c.error, Some(DEMO_CHALLENGE_ERROR_STR.into()));
226 assert_eq!(
227 c.error_description,
228 Some(DEMO_CHALLENGE_ERROR_DESCRIPTION_STR.into())
229 );
230 assert_eq!(c.error_uri, None);
231 assert_eq!(c.to_string(), DEMO_CHALLENGE_STR);
232 }
233 x => panic!("{x:?}"),
234 }
235
236 match DEMO_CHALLENGE_STR_SIMPLE.parse::<Challenges>() {
237 Ok(c) => {
238 assert_eq!(c.0.len(), 1);
239 let c = c.0.first().unwrap();
240 let c = c.as_bearer().unwrap();
241 assert_eq!(c.realm, DEMO_CHALLENGE_REALM_STR.into());
242 assert_eq!(c.scope, None);
243 assert_eq!(c.error, None);
244 assert_eq!(c.error_description, None);
245 assert_eq!(c.error_uri, None);
246 assert_eq!(c.to_string(), DEMO_CHALLENGE_STR_SIMPLE);
247 }
248 x => panic!("{x:?}"),
249 }
250 }
251 #[cfg(not(feature = "scheme-bearer"))]
252 {
253 match "Bearer".parse::<Challenges>() {
254 Err(ChallengesParseError::SchemeUnsupported(_)) => {}
255 x => panic!("{x:?}"),
256 }
257 }
258
259 #[cfg(all(feature = "scheme-basic", feature = "scheme-bearer"))]
260 {
261 use crate::schemes::{basic, bearer};
262
263 let s = format!(
264 "{}, {}",
265 basic::DEMO_CHALLENGE_STR_SIMPLE,
266 bearer::DEMO_CHALLENGE_STR_SIMPLE
267 );
268
269 match s.parse::<Challenges>() {
270 Ok(c) => {
271 for (i, c) in c.iter().enumerate() {
272 match i {
273 0 => {
274 let c = c.as_basic().unwrap();
275 assert_eq!(c.realm, basic::DEMO_CHALLENGE_REALM_STR.into());
276 }
277 1 => {
278 let c = c.as_bearer().unwrap();
279 assert_eq!(c.realm, bearer::DEMO_CHALLENGE_REALM_STR.into());
280 }
281 i => panic!("{i} {c:?}"),
282 }
283 }
284
285 assert_eq!(c.to_string(), s);
286 }
287 x => panic!("{x:?}"),
288 }
289 }
290
291 match Challenges::from_str("") {
293 Err(ChallengesParseError::Other(_)) => {}
294 x => panic!("{x:?}"),
295 }
296
297 match Challenges::from_str("Foo") {
298 Err(ChallengesParseError::SchemeUnknown) => {}
299 x => panic!("{x:?}"),
300 }
301 }
302}