http_authentication/
challenges.rs

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//
21#[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//
100#[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
128//
129impl 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//
138//
139#[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
149//
150impl 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        //
171        #[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        //
211        #[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        //
292        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}