use str_reader::StringReader;
use ttpkit::header::HeaderFieldValue;
use crate::{Error, StringReaderExt as _};
#[derive(Debug, Clone)]
pub struct AuthChallenge {
scheme: String,
token: Option<String>,
params: Vec<ChallengeParam>,
}
impl AuthChallenge {
pub fn parse(header: &HeaderFieldValue) -> Result<Vec<Self>, Error> {
let challenge = header
.to_str()
.map_err(|_| Error::from_static_msg("authentication challenge is not UTF-8 encoded"))?;
let mut res = Vec::new();
let mut reader = StringReader::new(challenge);
while !reader.is_empty() {
if let Some(challenge) = reader.parse_challenge()? {
res.push(challenge);
}
reader.skip_whitespace();
while reader.current_char() == Some(',') {
reader.skip_char();
reader.skip_whitespace();
}
}
Ok(res)
}
#[inline]
pub fn scheme(&self) -> &str {
&self.scheme
}
#[inline]
pub fn token(&self) -> Option<&str> {
self.token.as_deref()
}
#[inline]
pub fn params(&self) -> &[ChallengeParam] {
&self.params
}
}
#[derive(Debug, Clone)]
pub struct ChallengeParam {
name: String,
value: String,
}
impl ChallengeParam {
#[inline]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn value(&self) -> &str {
&self.value
}
}
trait StringReaderExt {
fn parse_challenge(&mut self) -> Result<Option<AuthChallenge>, Error>;
fn parse_challenge_token(&mut self) -> Option<String>;
fn parse_challenge_param(&mut self) -> Result<Option<ChallengeParam>, Error>;
}
impl StringReaderExt for StringReader<'_> {
fn parse_challenge(&mut self) -> Result<Option<AuthChallenge>, Error> {
let mut reader = StringReader::new(self.as_str());
reader.skip_whitespace();
let name = reader.read_until(|c| c == ',' || c.is_whitespace());
if name.is_empty() {
*self = reader;
return Ok(None);
}
let mut challenge = AuthChallenge {
scheme: name.to_ascii_lowercase(),
token: None,
params: Vec::new(),
};
if let Some(token) = reader.parse_challenge_token() {
challenge.token = Some(token);
} else {
while let Some(param) = reader.parse_challenge_param()? {
challenge.params.push(param);
reader.skip_whitespace();
if let Some(c) = reader.current_char()
&& c != ','
{
reader.read_until(|c| c == ',');
}
reader.skip_char();
}
}
*self = reader;
Ok(Some(challenge))
}
fn parse_challenge_token(&mut self) -> Option<String> {
let mut reader = StringReader::new(self.as_str());
reader.skip_whitespace();
let token = reader.read_until(|c| c == ',' || c.is_whitespace());
if token.trim_end_matches('=').contains('=') || token.is_empty() {
return None;
}
*self = reader;
Some(token.into())
}
fn parse_challenge_param(&mut self) -> Result<Option<ChallengeParam>, Error> {
let mut reader = StringReader::new(self.as_str());
reader.skip_whitespace();
let name = reader.read_until(|c| c == '=' || c == ',' || c.is_whitespace());
reader.skip_whitespace();
if reader.match_char('=').is_err() {
return Ok(None);
} else if name.is_empty() {
return Err(Error::from_static_msg("empty challenge parameter name"));
}
reader.skip_whitespace();
let value = if reader.current_char() == Some('"') {
reader.parse_quoted_string()?
} else {
reader.read_until(|c| c == ',' || c.is_whitespace()).into()
};
let res = ChallengeParam {
name: name.to_ascii_lowercase(),
value,
};
*self = reader;
Ok(Some(res))
}
}