use std::collections::HashSet;
use bytes::Bytes;
use crate::error::HeaderError;
use crate::headers::grammar::{self, is_token_char, trim};
use crate::message::TypedHeader;
use crate::name::HeaderName;
const LABEL: &str = "Privacy";
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PrivacyValue {
User,
Header,
Session,
None,
Critical,
Id,
History,
Extension(Vec<u8>),
}
impl PrivacyValue {
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::User => b"user",
Self::Header => b"header",
Self::Session => b"session",
Self::None => b"none",
Self::Critical => b"critical",
Self::Id => b"id",
Self::History => b"history",
Self::Extension(value) => value,
}
}
fn decode(value: &[u8]) -> Result<Self, HeaderError> {
if value.is_empty() || !value.iter().all(|&octet| is_token_char(octet)) {
return Err(syntax());
}
Ok(known(value).unwrap_or_else(|| Self::Extension(value.to_vec())))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Privacy(PrivacyValue);
impl Privacy {
pub fn new(value: PrivacyValue) -> Result<Self, HeaderError> {
validate_value(&value)?;
Ok(Self(value))
}
#[must_use]
pub fn value(&self) -> &PrivacyValue {
&self.0
}
#[must_use]
pub fn is(&self, wanted: &PrivacyValue) -> bool {
self.0.as_bytes().eq_ignore_ascii_case(wanted.as_bytes())
}
#[must_use]
pub fn to_bytes(&self) -> Bytes {
Bytes::copy_from_slice(self.0.as_bytes())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrivacyList(Vec<Privacy>);
impl PrivacyList {
pub fn new(values: impl IntoIterator<Item = PrivacyValue>) -> Result<Self, HeaderError> {
let values = values
.into_iter()
.map(Privacy::new)
.collect::<Result<Vec<_>, _>>()?;
validate(values.iter().map(|privacy| &privacy.0))?;
Ok(Self(values))
}
#[must_use]
pub fn values(&self) -> &[Privacy] {
&self.0
}
#[must_use]
pub fn contains(&self, wanted: &PrivacyValue) -> bool {
self.0.iter().any(|value| value.is(wanted))
}
#[must_use]
pub fn to_bytes(&self) -> Bytes {
let mut out = Vec::new();
for (position, value) in self.0.iter().enumerate() {
if position != 0 {
out.push(b',');
}
out.extend_from_slice(value.0.as_bytes());
}
Bytes::from(out)
}
}
impl TypedHeader for Privacy {
const NAME: HeaderName = HeaderName::Privacy;
const VALIDATE_LIST: bool = true;
fn decode(value: &[u8]) -> Result<Self, HeaderError> {
PrivacyValue::decode(trim(value)).map(Self)
}
fn decode_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
grammar::split_list(value, LABEL)?
.into_iter()
.map(Self::decode)
.collect()
}
fn validate_list(values: &[&Self]) -> Result<(), HeaderError> {
validate(values.iter().map(|privacy| &privacy.0))
}
}
fn validate_value(value: &PrivacyValue) -> Result<(), HeaderError> {
let token = value.as_bytes();
if token.is_empty() || !token.iter().all(|&octet| is_token_char(octet)) {
return Err(syntax());
}
if matches!(value, PrivacyValue::Extension(_)) && known(token).is_some() {
return Err(syntax());
}
Ok(())
}
fn validate<'a>(values: impl IntoIterator<Item = &'a PrivacyValue>) -> Result<(), HeaderError> {
let values = values.into_iter().collect::<Vec<_>>();
if values.is_empty() {
return Err(syntax());
}
let mut seen = HashSet::with_capacity(values.len());
let mut none = false;
let mut critical = None;
for (position, value) in values.iter().enumerate() {
validate_value(value)?;
let token = value.as_bytes();
if !seen.insert(token.to_ascii_lowercase()) {
return Err(syntax());
}
match *value {
PrivacyValue::None => none = true,
PrivacyValue::Critical => critical = Some(position),
_ => {}
}
}
if none && values.len() != 1 {
return Err(syntax());
}
if let Some(position) = critical
&& (position == 0 || position + 1 != values.len())
{
return Err(syntax());
}
Ok(())
}
fn known(value: &[u8]) -> Option<PrivacyValue> {
if value.eq_ignore_ascii_case(b"user") {
Some(PrivacyValue::User)
} else if value.eq_ignore_ascii_case(b"header") {
Some(PrivacyValue::Header)
} else if value.eq_ignore_ascii_case(b"session") {
Some(PrivacyValue::Session)
} else if value.eq_ignore_ascii_case(b"none") {
Some(PrivacyValue::None)
} else if value.eq_ignore_ascii_case(b"critical") {
Some(PrivacyValue::Critical)
} else if value.eq_ignore_ascii_case(b"id") {
Some(PrivacyValue::Id)
} else if value.eq_ignore_ascii_case(b"history") {
Some(PrivacyValue::History)
} else {
None
}
}
fn syntax() -> HeaderError {
HeaderError::Syntax { header: LABEL }
}