Skip to main content

sipx_sip/headers/
identity.rs

1//! RFC 3325 asserted and preferred identity header values.
2//!
3//! Trust is deliberately not represented here. Construction enforces RFC 3325's strict
4//! one-or-two-value shape. Reception follows RFC 5876 §4.5 instead: syntactically valid values
5//! with an unexpected scheme or position are reported as ignored so a proxy can remove them
6//! without discarding the valid identities that preceded them.
7
8use bytes::Bytes;
9
10use crate::error::HeaderError;
11use crate::headers::address::Address;
12use crate::headers::grammar;
13use crate::message::{Headers, TypedHeader};
14use crate::name::HeaderName;
15use crate::uri::Scheme;
16
17/// Why RFC 5876 §4.5 says a received identity value is ignored.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[non_exhaustive]
20pub enum IgnoredIdentityReason {
21    /// The URI is not SIP, SIPS or TEL.
22    UnexpectedScheme,
23    /// A SIP URI already occurred earlier in the field.
24    DuplicateSip,
25    /// A SIPS URI already occurred earlier in the field.
26    DuplicateSips,
27    /// A TEL URI already occurred earlier in the field.
28    DuplicateTel,
29    /// A SIPS URI occurred earlier, so this SIP URI is an unexpected combination.
30    SipAfterSips,
31    /// A SIP URI occurred earlier, so this SIPS URI is an unexpected combination.
32    SipsAfterSip,
33}
34
35/// One syntactically parsed received identity that RFC 5876 §4.5 says not to use or forward.
36#[derive(Debug, Clone)]
37pub struct IgnoredIdentity {
38    index: usize,
39    address: Address,
40    reason: IgnoredIdentityReason,
41}
42
43impl IgnoredIdentity {
44    /// Zero-based position in the combined field, across comma-joined values and repeated rows.
45    #[must_use]
46    pub fn index(&self) -> usize {
47        self.index
48    }
49
50    /// The parsed value. An unexpected scheme remains available for diagnostics and policy logs.
51    #[must_use]
52    pub fn address(&self) -> &Address {
53        &self.address
54    }
55
56    /// The RFC 5876 filtering rule that matched this value.
57    #[must_use]
58    pub fn reason(&self) -> IgnoredIdentityReason {
59        self.reason
60    }
61}
62
63fn validate_common(address: &Address, header: &'static str) -> Result<(), HeaderError> {
64    // RFC 8217 updates RFC 3325: a URI containing `,`, `;` or `?` has to use name-addr.
65    // Address::parse therefore interprets a bare semicolon tail as header parameters. RFC 3325
66    // defines no such parameters, so every non-empty tail is malformed rather than URI content.
67    if !address.params.is_empty() {
68        return Err(HeaderError::Syntax { header });
69    }
70
71    if let Some(display_name) = &address.display_name {
72        // `Address` predates checked construction and exposes its fields. Keep the identity
73        // wrapper's private-field invariant meaningful even for a manually assembled Address:
74        // serialization writes printable ASCII (escaping quote and backslash), horizontal tab,
75        // or well-formed UTF-8, never a control byte that could alter a field line.
76        let valid_ascii = display_name.iter().all(|byte| {
77            byte.is_ascii_graphic() || matches!(byte, b' ' | b'\t') || !byte.is_ascii()
78        });
79        if !valid_ascii || std::str::from_utf8(display_name).is_err() {
80            return Err(HeaderError::Syntax { header });
81        }
82    }
83
84    Ok(())
85}
86
87fn validate_address(address: &Address, header: &'static str) -> Result<(), HeaderError> {
88    validate_common(address, header)?;
89    if !matches!(
90        address.uri.scheme(),
91        Scheme::Sip | Scheme::Sips | Scheme::Tel
92    ) {
93        return Err(HeaderError::Syntax { header });
94    }
95    Ok(())
96}
97
98fn parse_received_value(value: &[u8], header: &'static str) -> Result<Address, HeaderError> {
99    let address = Address::parse(value, header)?;
100    validate_common(&address, header)?;
101    Ok(address)
102}
103
104fn parse_value(value: &[u8], header: &'static str) -> Result<Address, HeaderError> {
105    let address = parse_received_value(value, header)?;
106    validate_address(&address, header)?;
107    Ok(address)
108}
109
110fn parse_list(value: &[u8], header: &'static str) -> Result<Vec<Address>, HeaderError> {
111    grammar::split_list(value, header)?
112        .into_iter()
113        .map(|part| parse_value(part, header))
114        .collect()
115}
116
117fn validate_list<'a>(
118    values: impl IntoIterator<Item = &'a Address>,
119    header: &'static str,
120) -> Result<(), HeaderError> {
121    let mut values = values.into_iter();
122    let first = values.next().ok_or(HeaderError::Syntax { header })?;
123    let second = values.next();
124    if values.next().is_some() {
125        return Err(HeaderError::Syntax { header });
126    }
127    match second {
128        None => Ok(()),
129        Some(second)
130            if is_sip_family(first) != is_sip_family(second) && is_tel(first) != is_tel(second) =>
131        {
132            Ok(())
133        }
134        Some(_) => Err(HeaderError::Syntax { header }),
135    }
136}
137
138#[must_use]
139fn is_sip_family(address: &Address) -> bool {
140    matches!(address.uri.scheme(), Scheme::Sip | Scheme::Sips)
141}
142
143#[must_use]
144fn is_tel(address: &Address) -> bool {
145    matches!(address.uri.scheme(), Scheme::Tel)
146}
147
148fn serialize(address: &Address) -> Bytes {
149    let mut out = Vec::new();
150    if let Some(display_name) = &address.display_name {
151        out.push(b'"');
152        for &byte in display_name {
153            if matches!(byte, b'"' | b'\\') {
154                out.push(b'\\');
155            }
156            out.push(byte);
157        }
158        out.extend_from_slice(b"\" ");
159    }
160    out.push(b'<');
161    address.uri.write_to(&mut out);
162    out.push(b'>');
163    Bytes::from(out)
164}
165
166#[derive(Default)]
167struct SeenSchemes {
168    sip: bool,
169    sips: bool,
170    tel: bool,
171}
172
173impl SeenSchemes {
174    fn classify(&mut self, address: &Address) -> Option<IgnoredIdentityReason> {
175        match address.uri.scheme() {
176            Scheme::Sip if self.sip => Some(IgnoredIdentityReason::DuplicateSip),
177            Scheme::Sip => {
178                self.sip = true;
179                self.sips.then_some(IgnoredIdentityReason::SipAfterSips)
180            }
181            Scheme::Sips if self.sips => Some(IgnoredIdentityReason::DuplicateSips),
182            Scheme::Sips => {
183                self.sips = true;
184                self.sip.then_some(IgnoredIdentityReason::SipsAfterSip)
185            }
186            Scheme::Tel if self.tel => Some(IgnoredIdentityReason::DuplicateTel),
187            Scheme::Tel => {
188                self.tel = true;
189                None
190            }
191            Scheme::Other(_) => Some(IgnoredIdentityReason::UnexpectedScheme),
192        }
193    }
194}
195
196struct ReceivedIdentityList {
197    values: Vec<Address>,
198    ignored: Vec<IgnoredIdentity>,
199}
200
201fn receive_list(
202    headers: &Headers,
203    name: &HeaderName,
204    label: &'static str,
205) -> Result<Option<ReceivedIdentityList>, HeaderError> {
206    let mut present = false;
207    let mut index = 0usize;
208    let mut seen = SeenSchemes::default();
209    let mut values = Vec::new();
210    let mut ignored = Vec::new();
211
212    for row in headers.get_all(name) {
213        present = true;
214        let value = row.value();
215        for part in grammar::split_list(value.as_ref(), label)? {
216            let address = parse_received_value(part, label)?;
217            if let Some(reason) = seen.classify(&address) {
218                ignored.push(IgnoredIdentity {
219                    index,
220                    address,
221                    reason,
222                });
223            } else {
224                values.push(address);
225            }
226            index += 1;
227        }
228    }
229
230    Ok(present.then_some(ReceivedIdentityList { values, ignored }))
231}
232
233macro_rules! identity_header {
234    (
235        $(#[$meta:meta])*
236        $type:ident, $list:ident => $variant:ident, $label:literal
237    ) => {
238        $(#[$meta])*
239        #[derive(Debug, Clone)]
240        pub struct $type(Address);
241
242        impl $type {
243            /// Construct one strict RFC 3325 value from a parsed address.
244            ///
245            /// Only SIP, SIPS and TEL are accepted, and RFC 8217's name-addr rule is enforced.
246            /// Use the checked complete-list type when constructing a whole field.
247            pub fn new(address: Address) -> Result<Self, HeaderError> {
248                validate_address(&address, $label)?;
249                Ok(Self(address))
250            }
251
252            /// The parsed identity address.
253            #[must_use]
254            pub fn address(&self) -> &Address {
255                &self.0
256            }
257
258            /// Serialize this identity as an unambiguous name-address value.
259            #[must_use]
260            pub fn to_bytes(&self) -> Bytes {
261                serialize(&self.0)
262            }
263        }
264
265        impl std::ops::Deref for $type {
266            type Target = Address;
267
268            fn deref(&self) -> &Address {
269                &self.0
270            }
271        }
272
273        impl TypedHeader for $type {
274            const NAME: HeaderName = HeaderName::$variant;
275            const VALIDATE_LIST: bool = true;
276
277            fn decode(value: &[u8]) -> Result<Self, HeaderError> {
278                parse_value(value, $label).map(Self)
279            }
280
281            fn decode_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
282                parse_list(value, $label).map(|values| values.into_iter().map(Self).collect())
283            }
284
285            fn validate_list(values: &[&Self]) -> Result<(), HeaderError> {
286                validate_list(values.iter().map(|value| &value.0), $label)
287            }
288        }
289
290        /// A complete identity field: strict for construction, tolerant and explicit on receive.
291        #[derive(Debug, Clone)]
292        pub struct $list {
293            values: Vec<$type>,
294            ignored: Vec<IgnoredIdentity>,
295        }
296
297        impl $list {
298            /// Construct a complete RFC 3325 field, enforcing its one-or-two-value invariant.
299            pub fn new(
300                values: impl IntoIterator<Item = $type>,
301            ) -> Result<Self, HeaderError> {
302                let values = values.into_iter().collect::<Vec<_>>();
303                validate_list(values.iter().map(|value| &value.0), $label)?;
304                Ok(Self {
305                    values,
306                    ignored: Vec::new(),
307                })
308            }
309
310            /// Decode all received rows in wire order using RFC 5876 §4.5 filtering.
311            ///
312            /// `Ok(None)` means the field is absent. Unexpected schemes, duplicate schemes and
313            /// a SIP/SIPS combination are not syntax errors: they appear in [`Self::ignored`]. A
314            /// forwarding proxy must remove those indexed values before sending the request;
315            /// applying several removals in descending index order keeps earlier indices stable.
316            pub fn from_headers(headers: &Headers) -> Result<Option<Self>, HeaderError> {
317                receive_list(headers, &HeaderName::$variant, $label).map(|received| {
318                    received.map(|received| Self {
319                        values: received.values.into_iter().map($type).collect(),
320                        ignored: received.ignored,
321                    })
322                })
323            }
324
325            /// Values that may be used and forwarded, in their received or construction order.
326            #[must_use]
327            pub fn values(&self) -> &[$type] {
328                &self.values
329            }
330
331            /// Values RFC 5876 says to ignore and not forward, with stable wire-order indices.
332            ///
333            /// Remove several values from [`Headers`] in reverse order so each remaining index
334            /// still refers to the field shape this report describes.
335            #[must_use]
336            pub fn ignored(&self) -> &[IgnoredIdentity] {
337                &self.ignored
338            }
339
340            /// Whether forwarding the original field unchanged would violate RFC 5876 §4.5.
341            #[must_use]
342            pub fn requires_rewrite(&self) -> bool {
343                !self.ignored.is_empty()
344            }
345
346            /// Consume the list and return the usable values plus the ignored-value report.
347            #[must_use]
348            pub fn into_parts(self) -> (Vec<$type>, Vec<IgnoredIdentity>) {
349                (self.values, self.ignored)
350            }
351
352            /// Serialize the usable values as one deterministic comma-and-space-delimited row.
353            ///
354            /// Ignored received values are deliberately absent: RFC 5876 forbids forwarding them.
355            /// `None` means every received value was ignored, so a proxy removes the field rather
356            /// than constructing an invalid empty row.
357            #[must_use]
358            pub fn to_bytes(&self) -> Option<Bytes> {
359                if self.values.is_empty() {
360                    return None;
361                }
362                let mut out = Vec::new();
363                for (position, value) in self.values.iter().enumerate() {
364                    if position != 0 {
365                        out.extend_from_slice(b", ");
366                    }
367                    out.extend_from_slice(&value.to_bytes());
368                }
369                Some(Bytes::from(out))
370            }
371        }
372    };
373}
374
375identity_header!(
376    /// One strict `P-Asserted-Identity` value (RFC 3325 §9.1).
377    ///
378    /// [`PAssertedIdentityList`] is the complete construction and receive-list API.
379    PAssertedIdentity, PAssertedIdentityList => PAssertedIdentity, "P-Asserted-Identity"
380);
381identity_header!(
382    /// One strict `P-Preferred-Identity` value (RFC 3325 §9.2).
383    ///
384    /// [`PPreferredIdentityList`] is the complete construction and receive-list API.
385    PPreferredIdentity, PPreferredIdentityList => PPreferredIdentity, "P-Preferred-Identity"
386);