use std::fmt;
use sip_header_catalog::SipHeader;
use crate::diagnostic::{Field, ParseWarning, Parsed, WarningCode};
use crate::error::{FaultCode, ParseError};
use crate::header::TypedHeader;
use crate::scrub::scrub;
use crate::{QuoteStart, RowEntry};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TokenList {
header: SipHeader,
tokens: Vec<String>,
}
impl TokenList {
pub fn new(
header: SipHeader,
tokens: impl IntoIterator<Item = impl AsRef<str>>,
) -> Result<Self, ParseError> {
let mut list = Self::empty(header)?;
for token in tokens {
list.push(token)?;
}
if list
.tokens
.is_empty()
&& !may_be_empty(header)
{
return Err(ParseError::empty(Field::Value));
}
Ok(list)
}
pub fn parse_with_warnings(header: SipHeader, raw: &str) -> Result<Parsed<Self>, ParseError> {
Self::read(header, crate::split_row(raw, None, QuoteStart::Param))
}
pub fn parse(header: SipHeader, raw: &str) -> Result<Self, ParseError> {
Self::parse_with_warnings(header, raw).map(|p| p.value)
}
pub fn parse_strict(header: SipHeader, raw: &str) -> Result<Self, ParseError> {
Self::parse_with_warnings(header, raw)?.into_strict()
}
pub fn from_entries_with_warnings<'a>(
header: SipHeader,
entries: impl IntoIterator<Item = &'a str>,
) -> Result<Parsed<Self>, ParseError> {
Self::read(
header,
entries
.into_iter()
.enumerate()
.map(|(i, e)| RowEntry::whole(i, e)),
)
}
pub fn from_entries<'a>(
header: SipHeader,
entries: impl IntoIterator<Item = &'a str>,
) -> Result<Self, ParseError> {
Self::from_entries_with_warnings(header, entries).map(|p| p.value)
}
pub fn from_entries_strict<'a>(
header: SipHeader,
entries: impl IntoIterator<Item = &'a str>,
) -> Result<Self, ParseError> {
Self::from_entries_with_warnings(header, entries)?.into_strict()
}
pub fn from_rows_with_warnings<'a>(
header: SipHeader,
rows: impl IntoIterator<Item = &'a str>,
) -> Result<Parsed<Self>, ParseError> {
Self::read(header, crate::row_entries(rows, QuoteStart::Param))
}
pub fn from_rows<'a>(
header: SipHeader,
rows: impl IntoIterator<Item = &'a str>,
) -> Result<Self, ParseError> {
Self::from_rows_with_warnings(header, rows).map(|p| p.value)
}
pub fn from_rows_strict<'a>(
header: SipHeader,
rows: impl IntoIterator<Item = &'a str>,
) -> Result<Self, ParseError> {
Self::from_rows_with_warnings(header, rows)?.into_strict()
}
pub fn header(&self) -> SipHeader {
self.header
}
pub fn iter(&self) -> std::iter::Map<std::slice::Iter<'_, String>, fn(&String) -> &str> {
self.tokens
.iter()
.map(String::as_str)
}
pub fn len(&self) -> usize {
self.tokens
.len()
}
pub fn is_empty(&self) -> bool {
self.tokens
.is_empty()
}
pub fn contains(&self, token: &str) -> bool {
self.iter()
.any(|t| {
if self.is_case_sensitive() {
t == token
} else {
t.eq_ignore_ascii_case(token)
}
})
}
pub fn is_case_sensitive(&self) -> bool {
matches!(
self.header,
SipHeader::Allow | SipHeader::InReplyTo | SipHeader::AllowEvents
)
}
pub fn push(&mut self, token: impl AsRef<str>) -> Result<(), ParseError> {
let token = checked(self.header, token.as_ref()).map_err(|e| e.in_entry(self.len()))?;
self.tokens
.push(token);
Ok(())
}
pub fn remove(&mut self, index: usize) -> Result<Option<String>, ParseError> {
if may_be_empty(self.header) {
Ok((index < self.len()).then(|| {
self.tokens
.remove(index)
}))
} else {
crate::list::remove_needed(&mut self.tokens, index)
}
}
pub fn retain(&mut self, mut keep: impl FnMut(&str) -> bool) -> Result<(), ParseError> {
if may_be_empty(self.header) {
self.tokens
.retain(|t| keep(t));
Ok(())
} else {
crate::list::retain_needed(&mut self.tokens, |t| keep(t))
}
}
fn empty(header: SipHeader) -> Result<Self, ParseError> {
if !<Self as TypedHeader>::HEADERS.contains(&header) {
return Err(ParseError::malformed(
Field::Value,
FaultCode::WrongHeader,
None,
));
}
Ok(TokenList {
header,
tokens: Vec::new(),
})
}
fn read<'a>(
header: SipHeader,
entries: impl IntoIterator<Item = RowEntry<'a>>,
) -> Result<Parsed<Self>, ParseError> {
let mut list = Self::empty(header)?;
let entries: Vec<RowEntry<'a>> = entries
.into_iter()
.collect();
let comma = |i: usize, entry: &RowEntry<'_>| {
entry
.final_comma()
.into_iter()
.map(move |w| w.in_entry(i))
};
let mut warnings = Vec::new();
if entries
.iter()
.all(|e| {
scrub(e.text)
.text
.trim()
.is_empty()
})
{
if !may_be_empty(header) {
return Err(ParseError::empty(Field::Value));
}
let lone = matches!(entries.as_slice(), [e] if e.comma.is_none());
for (i, e) in entries
.iter()
.enumerate()
{
let empty = (!lone).then(|| crate::empty_entry(Field::Entry, 0));
warnings.extend(
scrub(e.text)
.warnings
.into_iter()
.chain(empty)
.map(|w| {
w.relocate(&e.relocation())
.in_entry(i)
})
.chain(comma(i, e)),
);
}
return Ok(Parsed::new(list, warnings));
}
for (i, entry) in entries
.iter()
.enumerate()
{
let mut found = Vec::new();
if let Some(token) = read_token(header, entry.text, &mut found) {
list.tokens
.push(token);
}
warnings.extend(
found
.into_iter()
.map(|w| {
w.relocate(&entry.relocation())
.in_entry(i)
})
.chain(comma(i, entry)),
);
}
if list
.tokens
.is_empty()
&& !may_be_empty(header)
{
return Err(ParseError::empty(Field::Value));
}
Ok(Parsed::new(list, warnings))
}
}
fn may_be_empty(header: SipHeader) -> bool {
matches!(header, SipHeader::Allow | SipHeader::Supported)
}
fn checked(header: SipHeader, token: &str) -> Result<String, ParseError> {
if header == SipHeader::InReplyTo {
crate::check::refuse(Field::CallId, token, &crate::FRAMING)?;
Ok(crate::SipCallId::new(token)?.into())
} else {
crate::check::checked_token(Field::Entry, token)
}
}
fn read_token(header: SipHeader, entry: &str, warnings: &mut Vec<ParseWarning>) -> Option<String> {
let scrubbed = scrub(entry);
warnings.extend(
scrubbed
.warnings
.iter()
.copied(),
);
let mut found = Vec::new();
let text: &str = &scrubbed.text;
let token = crate::token_field(text, text.trim(), Field::Entry, &mut found).into_owned();
if token.is_empty() {
warnings.push(crate::empty_entry(Field::Entry, 0));
return None;
}
let at = crate::offset_in(text, text.trim());
if header == SipHeader::InReplyTo {
crate::call_id::report_breach(&token, at, &mut found);
} else if !crate::is_token(&token) {
found.push(ParseWarning::new(Field::Entry, WarningCode::InvalidToken).at(at));
}
warnings.extend(
found
.into_iter()
.map(|w| w.relocate(&scrubbed.relocation())),
);
Some(token)
}
#[cfg(feature = "serde")]
serde_parts!(TokenList, TokenListParts);
#[cfg(feature = "serde")]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct TokenListParts {
header: SipHeader,
#[serde(deserialize_with = "crate::serde_parts::field::tokens")]
tokens: Vec<String>,
}
#[cfg(feature = "serde")]
impl TokenListParts {
fn into_value(p: Self) -> Result<TokenList, ParseError> {
TokenList::new(p.header, p.tokens)
}
fn from_value(list: TokenList) -> Self {
TokenListParts {
header: list.header,
tokens: list.tokens,
}
}
}
impl fmt::Display for TokenList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
crate::fmt_joined(f, &self.tokens, ", ")
}
}
impl IntoIterator for TokenList {
type Item = String;
type IntoIter = std::vec::IntoIter<String>;
fn into_iter(self) -> Self::IntoIter {
self.tokens
.into_iter()
}
}
impl<'a> IntoIterator for &'a TokenList {
type Item = &'a str;
type IntoIter = std::iter::Map<std::slice::Iter<'a, String>, fn(&String) -> &str>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}