Skip to main content

sip_header/
contact.rs

1//! SIP Contact header value parser (RFC 3261 §20.10).
2//!
3//! Contact can be either `*` (wildcard, used in REGISTER with Expires: 0)
4//! or a comma-separated list of `name-addr / addr-spec` entries with
5//! optional parameters.
6
7use crate::header_addr::{ParseSipHeaderAddrError, SipHeaderAddr};
8use std::fmt;
9
10/// A single Contact header value: either the `*` wildcard or an address.
11#[derive(Debug, Clone, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum ContactValue {
14    /// The `*` wildcard (RFC 3261 §10.2.2, used in REGISTER).
15    Wildcard,
16    /// A `name-addr` or `addr-spec` with optional contact parameters.
17    Addr(Box<SipHeaderAddr>),
18}
19
20impl fmt::Display for ContactValue {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            Self::Wildcard => f.write_str("*"),
24            Self::Addr(addr) => write!(f, "{addr}"),
25        }
26    }
27}
28
29/// Parse a single contact entry (after comma-splitting).
30fn parse_contact_entry(raw: &str) -> Result<ContactValue, ParseSipHeaderAddrError> {
31    let trimmed = raw.trim();
32    if trimmed == "*" {
33        Ok(ContactValue::Wildcard)
34    } else {
35        trimmed
36            .parse::<SipHeaderAddr>()
37            .map(|a| ContactValue::Addr(Box::new(a)))
38    }
39}
40
41/// Parse a comma-separated Contact header value into a list of [`ContactValue`].
42pub fn parse_contact_list(raw: &str) -> Result<Vec<ContactValue>, ParseSipHeaderAddrError> {
43    let trimmed = raw.trim();
44    if trimmed == "*" {
45        return Ok(vec![ContactValue::Wildcard]);
46    }
47    crate::split_comma_entries(trimmed)
48        .into_iter()
49        .map(parse_contact_entry)
50        .collect()
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn wildcard() {
59        let contacts = parse_contact_list("*").unwrap();
60        assert_eq!(contacts.len(), 1);
61        assert!(matches!(contacts[0], ContactValue::Wildcard));
62    }
63
64    #[test]
65    fn single_addr() {
66        let contacts = parse_contact_list("<sip:alice@198.51.100.1>").unwrap();
67        assert_eq!(contacts.len(), 1);
68        match &contacts[0] {
69            ContactValue::Addr(addr) => {
70                assert!(addr
71                    .uri()
72                    .to_string()
73                    .contains("alice"));
74            }
75            _ => panic!("expected Addr"),
76        }
77    }
78
79    #[test]
80    fn multiple_addrs() {
81        let contacts =
82            parse_contact_list("<sip:alice@198.51.100.1>, \"Bob\" <sip:bob@198.51.100.2>").unwrap();
83        assert_eq!(contacts.len(), 2);
84        match &contacts[1] {
85            ContactValue::Addr(addr) => {
86                assert_eq!(addr.display_name(), Some("Bob"));
87            }
88            _ => panic!("expected Addr"),
89        }
90    }
91
92    #[test]
93    fn display_wildcard() {
94        assert_eq!(ContactValue::Wildcard.to_string(), "*");
95    }
96
97    #[test]
98    fn display_addr() {
99        let addr = "sip:alice@198.51.100.1"
100            .parse::<sip_uri::Uri>()
101            .unwrap();
102        let cv = ContactValue::Addr(Box::new(SipHeaderAddr::new(addr)));
103        assert!(cv
104            .to_string()
105            .contains("alice"));
106    }
107}