Skip to main content

sipx_sip/headers/
privacy.rs

1//! Privacy preferences (RFC 3323 §4.2 and verified erratum 5184).
2//!
3//! The delimiter and construction rules live here so applications and forwarding policy consume
4//! typed values rather than each growing a subtly different comma-list parser.
5
6use std::collections::HashSet;
7
8use bytes::Bytes;
9
10use crate::error::HeaderError;
11use crate::headers::grammar::{self, is_token_char, trim};
12use crate::message::TypedHeader;
13use crate::name::HeaderName;
14
15const LABEL: &str = "Privacy";
16
17/// One value from a `Privacy` header.
18///
19/// The first seven variants are the values in the IANA SIP Privacy Header Field Values registry.
20/// `Extension` retains the spelling of a later token so policy can recognize it without waiting
21/// for this enum to gain another variant.
22#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub enum PrivacyValue {
24    /// Request user-level privacy.
25    User,
26    /// Request privacy for identifying routing headers.
27    Header,
28    /// Request privacy for session media.
29    Session,
30    /// Explicitly request no privacy service.
31    None,
32    /// Require requested privacy services to succeed or the request to fail.
33    Critical,
34    /// Request privacy for asserted identity (RFC 3325 §9.3).
35    Id,
36    /// Request privacy for History-Info (RFC 7044 §10.1).
37    History,
38    /// A later registered token, preserving its spelling.
39    Extension(Vec<u8>),
40}
41
42impl PrivacyValue {
43    /// The token spelling used for deterministic serialization.
44    #[must_use]
45    pub fn as_bytes(&self) -> &[u8] {
46        match self {
47            Self::User => b"user",
48            Self::Header => b"header",
49            Self::Session => b"session",
50            Self::None => b"none",
51            Self::Critical => b"critical",
52            Self::Id => b"id",
53            Self::History => b"history",
54            Self::Extension(value) => value,
55        }
56    }
57
58    fn decode(value: &[u8]) -> Result<Self, HeaderError> {
59        if value.is_empty() || !value.iter().all(|&octet| is_token_char(octet)) {
60            return Err(syntax());
61        }
62        Ok(known(value).unwrap_or_else(|| Self::Extension(value.to_vec())))
63    }
64}
65
66/// One value from the message-wide comma-delimited `Privacy` list.
67///
68/// Use [`crate::message::Headers::typed_all`] to decode and validate the complete list across
69/// comma-joined and repeated rows. [`PrivacyList`] is the checked construction counterpart.
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct Privacy(PrivacyValue);
72
73impl Privacy {
74    /// Construct one syntactically valid privacy-list element.
75    pub fn new(value: PrivacyValue) -> Result<Self, HeaderError> {
76        validate_value(&value)?;
77        Ok(Self(value))
78    }
79
80    /// The typed privacy-list element.
81    #[must_use]
82    pub fn value(&self) -> &PrivacyValue {
83        &self.0
84    }
85
86    /// Whether this is the requested value, using case-insensitive token comparison.
87    #[must_use]
88    pub fn is(&self, wanted: &PrivacyValue) -> bool {
89        self.0.as_bytes().eq_ignore_ascii_case(wanted.as_bytes())
90    }
91
92    /// Serialize this canonical list element.
93    #[must_use]
94    pub fn to_bytes(&self) -> Bytes {
95        Bytes::copy_from_slice(self.0.as_bytes())
96    }
97}
98
99/// A complete validated `Privacy` list for application construction.
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct PrivacyList(Vec<Privacy>);
102
103impl PrivacyList {
104    /// Construct a complete list after enforcing its message-wide invariants.
105    pub fn new(values: impl IntoIterator<Item = PrivacyValue>) -> Result<Self, HeaderError> {
106        let values = values
107            .into_iter()
108            .map(Privacy::new)
109            .collect::<Result<Vec<_>, _>>()?;
110        validate(values.iter().map(|privacy| &privacy.0))?;
111        Ok(Self(values))
112    }
113
114    /// The requested values in construction order.
115    #[must_use]
116    pub fn values(&self) -> &[Privacy] {
117        &self.0
118    }
119
120    /// Whether a value occurs, using case-insensitive token comparison.
121    #[must_use]
122    pub fn contains(&self, wanted: &PrivacyValue) -> bool {
123        self.0.iter().any(|value| value.is(wanted))
124    }
125
126    /// Serialize one canonical comma-delimited header value.
127    #[must_use]
128    pub fn to_bytes(&self) -> Bytes {
129        let mut out = Vec::new();
130        for (position, value) in self.0.iter().enumerate() {
131            if position != 0 {
132                out.push(b',');
133            }
134            out.extend_from_slice(value.0.as_bytes());
135        }
136        Bytes::from(out)
137    }
138}
139
140impl TypedHeader for Privacy {
141    const NAME: HeaderName = HeaderName::Privacy;
142    const VALIDATE_LIST: bool = true;
143
144    fn decode(value: &[u8]) -> Result<Self, HeaderError> {
145        PrivacyValue::decode(trim(value)).map(Self)
146    }
147
148    fn decode_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
149        grammar::split_list(value, LABEL)?
150            .into_iter()
151            .map(Self::decode)
152            .collect()
153    }
154
155    fn validate_list(values: &[&Self]) -> Result<(), HeaderError> {
156        validate(values.iter().map(|privacy| &privacy.0))
157    }
158}
159
160fn validate_value(value: &PrivacyValue) -> Result<(), HeaderError> {
161    let token = value.as_bytes();
162    if token.is_empty() || !token.iter().all(|&octet| is_token_char(octet)) {
163        return Err(syntax());
164    }
165    if matches!(value, PrivacyValue::Extension(_)) && known(token).is_some() {
166        return Err(syntax());
167    }
168    Ok(())
169}
170
171fn validate<'a>(values: impl IntoIterator<Item = &'a PrivacyValue>) -> Result<(), HeaderError> {
172    let values = values.into_iter().collect::<Vec<_>>();
173    if values.is_empty() {
174        return Err(syntax());
175    }
176
177    let mut seen = HashSet::with_capacity(values.len());
178    let mut none = false;
179    let mut critical = None;
180    for (position, value) in values.iter().enumerate() {
181        validate_value(value)?;
182        let token = value.as_bytes();
183        if !seen.insert(token.to_ascii_lowercase()) {
184            return Err(syntax());
185        }
186        match *value {
187            PrivacyValue::None => none = true,
188            PrivacyValue::Critical => critical = Some(position),
189            _ => {}
190        }
191    }
192
193    if none && values.len() != 1 {
194        return Err(syntax());
195    }
196    if let Some(position) = critical
197        && (position == 0 || position + 1 != values.len())
198    {
199        return Err(syntax());
200    }
201    Ok(())
202}
203
204fn known(value: &[u8]) -> Option<PrivacyValue> {
205    if value.eq_ignore_ascii_case(b"user") {
206        Some(PrivacyValue::User)
207    } else if value.eq_ignore_ascii_case(b"header") {
208        Some(PrivacyValue::Header)
209    } else if value.eq_ignore_ascii_case(b"session") {
210        Some(PrivacyValue::Session)
211    } else if value.eq_ignore_ascii_case(b"none") {
212        Some(PrivacyValue::None)
213    } else if value.eq_ignore_ascii_case(b"critical") {
214        Some(PrivacyValue::Critical)
215    } else if value.eq_ignore_ascii_case(b"id") {
216        Some(PrivacyValue::Id)
217    } else if value.eq_ignore_ascii_case(b"history") {
218        Some(PrivacyValue::History)
219    } else {
220        None
221    }
222}
223
224fn syntax() -> HeaderError {
225    HeaderError::Syntax { header: LABEL }
226}