Skip to main content

sip_header/
via.rs

1//! SIP Via header parser (RFC 3261 §20.42).
2
3use std::fmt;
4
5/// Error parsing a SIP Via header.
6#[derive(Debug, Clone, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum SipViaError {
9    /// The Via header value is empty.
10    Empty,
11    /// The Via header value has an invalid format.
12    InvalidFormat(String),
13}
14
15impl fmt::Display for SipViaError {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match self {
18            Self::Empty => write!(f, "Via header is empty"),
19            Self::InvalidFormat(msg) => write!(f, "Invalid Via format: {}", msg),
20        }
21    }
22}
23
24impl std::error::Error for SipViaError {}
25
26/// A single Via entry.
27#[derive(Debug, Clone, PartialEq, Eq)]
28#[non_exhaustive]
29pub struct SipViaEntry {
30    protocol_name: String,
31    protocol_version: String,
32    transport: String,
33    host: String,
34    port: Option<u16>,
35    params: Vec<(String, Option<String>)>,
36    rport: Option<Option<u16>>,
37}
38
39impl SipViaEntry {
40    /// Returns the protocol name (e.g., "SIP").
41    pub fn protocol(&self) -> &str {
42        &self.protocol_name
43    }
44
45    /// Returns the protocol version (e.g., "2.0").
46    pub fn version(&self) -> &str {
47        &self.protocol_version
48    }
49
50    /// Returns the transport protocol (e.g., "UDP", "TCP", "TLS").
51    pub fn transport(&self) -> &str {
52        &self.transport
53    }
54
55    /// Returns the host.
56    pub fn host(&self) -> &str {
57        &self.host
58    }
59
60    /// Returns the port, if present.
61    pub fn port(&self) -> Option<u16> {
62        self.port
63    }
64
65    /// Returns all parameters.
66    pub fn params(&self) -> &[(String, Option<String>)] {
67        &self.params
68    }
69
70    /// Returns a specific parameter value by key (case-insensitive).
71    pub fn param(&self, key: &str) -> Option<Option<&str>> {
72        let key_lower = key.to_ascii_lowercase();
73        self.params
74            .iter()
75            .find(|(k, _)| k == &key_lower)
76            .map(|(_, v)| v.as_deref())
77    }
78
79    /// Returns the `branch` parameter value, if present.
80    pub fn branch(&self) -> Option<&str> {
81        self.param("branch")
82            .flatten()
83    }
84
85    /// Returns the `received` parameter value, if present.
86    pub fn received(&self) -> Option<&str> {
87        self.param("received")
88            .flatten()
89    }
90
91    /// Returns the `rport` parameter.
92    ///
93    /// - `None` if the parameter is absent
94    /// - `Some(None)` if present without a value
95    /// - `Some(Some(port))` if present with a value
96    ///
97    /// Invalid rport values are rejected at parse time, so this accessor
98    /// is infallible.
99    pub fn rport(&self) -> Option<Option<u16>> {
100        self.rport
101    }
102
103    fn parse(entry: &str) -> Result<Self, SipViaError> {
104        let trimmed = entry.trim();
105        if trimmed.is_empty() {
106            return Err(SipViaError::InvalidFormat("empty Via entry".to_string()));
107        }
108
109        // Split on first semicolon to separate sent-protocol/sent-by from params
110        let (main_part, params_part) = if let Some(semi_idx) = trimmed.find(';') {
111            (&trimmed[..semi_idx], Some(&trimmed[semi_idx + 1..]))
112        } else {
113            (trimmed, None)
114        };
115
116        let (protocol_name, protocol_version, transport, sent_by) = parse_sent_protocol(main_part)?;
117        let (host, port) = parse_host_port(sent_by)?;
118
119        let params: Vec<(String, Option<String>)> = crate::parse_params(params_part.unwrap_or(""))
120            .into_iter()
121            .map(|p| {
122                (
123                    p.key
124                        .to_ascii_lowercase(),
125                    p.value
126                        .map(str::to_string),
127                )
128            })
129            .collect();
130
131        let rport = params
132            .iter()
133            .find(|(k, _)| k == "rport")
134            .map(|(_, v)| match v {
135                None => Ok(None),
136                Some(s) => s
137                    .parse::<u16>()
138                    .map(Some)
139                    .map_err(|_| SipViaError::InvalidFormat("invalid rport value".to_string())),
140            })
141            .transpose()?;
142
143        Ok(Self {
144            protocol_name,
145            protocol_version,
146            transport,
147            host,
148            port,
149            params,
150            rport,
151        })
152    }
153}
154
155/// Split `sent-protocol LWS sent-by` into its parts, allowing SWS around
156/// each `/` (RFC 3261 §25.1 `SLASH`).
157fn parse_sent_protocol(main: &str) -> Result<(String, String, String, &str), SipViaError> {
158    let malformed =
159        || SipViaError::InvalidFormat("expected 'protocol/version/transport host[:port]'".into());
160    let (name_raw, rest) = main
161        .split_once('/')
162        .ok_or_else(malformed)?;
163    let (version_raw, after_slash) = rest
164        .split_once('/')
165        .ok_or_else(malformed)?;
166    let (name, version) = (name_raw.trim(), version_raw.trim());
167    let rest = after_slash.trim_start();
168    let (mut transport, mut sent_by) = rest.split_at(
169        rest.find(char::is_whitespace)
170            .unwrap_or(rest.len()),
171    );
172    sent_by = sent_by.trim();
173    // RFC 3261 §25.1 transport is a non-empty token; an empty one directly
174    // before sent-by (`SIP/2.0/ host`) stays accepted.
175    if sent_by.is_empty()
176        && after_slash.starts_with(char::is_whitespace)
177        && name == name_raw
178        && version == version_raw
179    {
180        (transport, sent_by) = ("", transport);
181    }
182    if sent_by.is_empty()
183        || [name, version, transport]
184            .iter()
185            .any(|p| p.contains(|c: char| c == '/' || c.is_whitespace()))
186    {
187        return Err(malformed());
188    }
189    Ok((name.into(), version.into(), transport.into(), sent_by))
190}
191
192impl fmt::Display for SipViaEntry {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        write!(
195            f,
196            "{}/{}/{}",
197            self.protocol_name, self.protocol_version, self.transport
198        )?;
199
200        // Handle IPv6 addresses with brackets
201        if self
202            .host
203            .contains(':')
204            && !self
205                .host
206                .starts_with('[')
207        {
208            write!(f, " [{}]", self.host)?;
209        } else {
210            write!(f, " {}", self.host)?;
211        }
212
213        if let Some(port) = self.port {
214            write!(f, ":{}", port)?;
215        }
216
217        for (key, value) in &self.params {
218            crate::write_param(f, key, value.as_deref(), false)?;
219        }
220
221        Ok(())
222    }
223}
224
225/// Parsed SIP Via header.
226#[derive(Debug, Clone, PartialEq, Eq)]
227#[non_exhaustive]
228pub struct SipVia {
229    entries: Vec<SipViaEntry>,
230}
231
232impl SipVia {
233    /// Parses a Via header value.
234    pub fn parse(raw: &str) -> Result<Self, SipViaError> {
235        let raw = raw.trim();
236        if raw.is_empty() {
237            return Err(SipViaError::Empty);
238        }
239        Self::from_entries(crate::split_comma_entries(raw))
240    }
241
242    /// Build from entries a transport already split; each is one `via-parm`.
243    pub fn from_entries<'a>(
244        entries: impl IntoIterator<Item = &'a str>,
245    ) -> Result<Self, SipViaError> {
246        let entries = entries
247            .into_iter()
248            .map(SipViaEntry::parse)
249            .collect::<Result<Vec<_>, _>>()?;
250        if entries.is_empty() {
251            return Err(SipViaError::Empty);
252        }
253        Ok(Self { entries })
254    }
255
256    /// Returns the Via entries.
257    pub fn entries(&self) -> &[SipViaEntry] {
258        &self.entries
259    }
260
261    /// Consume self and return entries as a `Vec`.
262    pub fn into_entries(self) -> Vec<SipViaEntry> {
263        self.entries
264    }
265
266    /// Returns the number of Via entries.
267    pub fn len(&self) -> usize {
268        self.entries
269            .len()
270    }
271
272    /// Returns `true` if there are no Via entries.
273    pub fn is_empty(&self) -> bool {
274        self.entries
275            .is_empty()
276    }
277}
278
279impl fmt::Display for SipVia {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        crate::fmt_joined(f, &self.entries, ", ")
282    }
283}
284
285impl_from_str_via_parse!(SipVia, SipViaError);
286
287impl IntoIterator for SipVia {
288    type Item = SipViaEntry;
289    type IntoIter = std::vec::IntoIter<SipViaEntry>;
290
291    fn into_iter(self) -> Self::IntoIter {
292        self.entries
293            .into_iter()
294    }
295}
296
297impl<'a> IntoIterator for &'a SipVia {
298    type Item = &'a SipViaEntry;
299    type IntoIter = std::slice::Iter<'a, SipViaEntry>;
300
301    fn into_iter(self) -> Self::IntoIter {
302        self.entries
303            .iter()
304    }
305}
306
307/// Split `sent-by = host [ COLON port ]`, allowing SWS around the colon.
308fn parse_host_port(sent_by: &str) -> Result<(String, Option<u16>), SipViaError> {
309    let invalid = |msg: &str| SipViaError::InvalidFormat(msg.to_string());
310    let (host, port) = if let Some(inner) = sent_by.strip_prefix('[') {
311        let close = inner
312            .find(']')
313            .ok_or_else(|| invalid("unclosed IPv6 bracket"))?;
314        let rest = inner[close + 1..].trim_start();
315        let port = if rest.is_empty() {
316            None
317        } else {
318            Some(
319                rest.strip_prefix(':')
320                    .ok_or_else(|| invalid("unexpected characters after IPv6 reference"))?,
321            )
322        };
323        (&inner[..close], port)
324    } else {
325        match sent_by.split_once(':') {
326            Some((_, port)) if port.contains(':') => {
327                return Err(invalid("IPv6 sent-by must be bracketed"));
328            }
329            Some((host, port)) => (host.trim_end(), Some(port)),
330            None => (sent_by, None),
331        }
332    };
333    if host.contains(char::is_whitespace) {
334        return Err(invalid("whitespace inside sent-by host"));
335    }
336    let port = port
337        .map(|p| {
338            p.trim_start()
339                .parse::<u16>()
340                .map_err(|_| {
341                    SipViaError::InvalidFormat(format!("invalid port ({} bytes)", p.len()))
342                })
343        })
344        .transpose()?;
345    Ok((host.to_string(), port))
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351
352    #[test]
353    fn test_single_via() {
354        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060").unwrap();
355        assert_eq!(via.len(), 1);
356
357        let entry = &via.entries()[0];
358        assert_eq!(entry.protocol(), "SIP");
359        assert_eq!(entry.version(), "2.0");
360        assert_eq!(entry.transport(), "UDP");
361        assert_eq!(entry.host(), "198.51.100.1");
362        assert_eq!(entry.port(), Some(5060));
363        assert!(entry
364            .params()
365            .is_empty());
366    }
367
368    #[test]
369    fn test_multiple_vias() {
370        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
371        assert_eq!(via.len(), 2);
372
373        let entry1 = &via.entries()[0];
374        assert_eq!(entry1.host(), "198.51.100.1");
375        assert_eq!(entry1.port(), Some(5060));
376        assert_eq!(entry1.transport(), "UDP");
377
378        let entry2 = &via.entries()[1];
379        assert_eq!(entry2.host(), "203.0.113.5");
380        assert_eq!(entry2.port(), None);
381        assert_eq!(entry2.transport(), "TCP");
382    }
383
384    #[test]
385    fn test_via_with_params() {
386        let via = SipVia::parse(
387            "SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bKnashds8;received=203.0.113.10;rport=5061",
388        )
389        .unwrap();
390
391        let entry = &via.entries()[0];
392        assert_eq!(entry.branch(), Some("z9hG4bKnashds8"));
393        assert_eq!(entry.received(), Some("203.0.113.10"));
394        assert_eq!(entry.rport(), Some(Some(5061)));
395    }
396
397    #[test]
398    fn test_via_with_rport_no_value() {
399        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060;rport").unwrap();
400
401        let entry = &via.entries()[0];
402        assert_eq!(entry.rport(), Some(None));
403    }
404
405    #[test]
406    fn test_via_without_rport() {
407        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060").unwrap();
408
409        let entry = &via.entries()[0];
410        assert_eq!(entry.rport(), None);
411    }
412
413    #[test]
414    fn test_via_ipv6() {
415        let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1]:5060").unwrap();
416
417        let entry = &via.entries()[0];
418        assert_eq!(entry.host(), "2001:db8::1");
419        assert_eq!(entry.port(), Some(5060));
420    }
421
422    #[test]
423    fn test_via_ipv6_no_port() {
424        let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1]").unwrap();
425
426        let entry = &via.entries()[0];
427        assert_eq!(entry.host(), "2001:db8::1");
428        assert_eq!(entry.port(), None);
429    }
430
431    #[test]
432    fn test_via_hostname() {
433        let via = SipVia::parse("SIP/2.0/TLS example.com:5061").unwrap();
434
435        let entry = &via.entries()[0];
436        assert_eq!(entry.host(), "example.com");
437        assert_eq!(entry.port(), Some(5061));
438        assert_eq!(entry.transport(), "TLS");
439    }
440
441    #[test]
442    fn test_empty_via() {
443        let result = SipVia::parse("");
444        assert!(matches!(result, Err(SipViaError::Empty)));
445    }
446
447    #[test]
448    fn test_empty_via_whitespace() {
449        let result = SipVia::parse("   ");
450        assert!(matches!(result, Err(SipViaError::Empty)));
451    }
452
453    #[test]
454    fn test_invalid_format() {
455        let result = SipVia::parse("invalid");
456        assert!(matches!(result, Err(SipViaError::InvalidFormat(_))));
457    }
458
459    #[test]
460    fn test_rport_invalid_value_is_error() {
461        let result = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060;rport=garbage");
462        assert!(result.is_err());
463    }
464
465    #[test]
466    fn test_display_roundtrip() {
467        let original =
468            "SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bKnashds8;received=203.0.113.10;rport";
469        let via = SipVia::parse(original).unwrap();
470        let displayed = via.to_string();
471
472        let reparsed = SipVia::parse(&displayed).unwrap();
473        assert_eq!(via, reparsed);
474    }
475
476    #[test]
477    fn test_display_multiple_vias() {
478        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
479        let displayed = via.to_string();
480        assert!(displayed.contains("198.51.100.1"));
481        assert!(displayed.contains("203.0.113.5"));
482    }
483
484    #[test]
485    fn test_into_iterator() {
486        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
487
488        let mut count = 0;
489        for entry in &via {
490            assert!(entry.host() == "198.51.100.1" || entry.host() == "203.0.113.5");
491            count += 1;
492        }
493        assert_eq!(count, 2);
494
495        let entries: Vec<_> = via
496            .into_iter()
497            .collect();
498        assert_eq!(entries.len(), 2);
499    }
500
501    #[test]
502    fn test_into_entries() {
503        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
504        let entries = via.into_entries();
505        assert_eq!(entries.len(), 2);
506        assert_eq!(entries[0].host(), "198.51.100.1");
507        assert_eq!(entries[1].host(), "203.0.113.5");
508    }
509
510    #[test]
511    fn test_from_str() {
512        let via: SipVia = "SIP/2.0/UDP 198.51.100.1:5060"
513            .parse()
514            .unwrap();
515        assert_eq!(via.len(), 1);
516    }
517
518    #[test]
519    fn test_param_case_insensitive() {
520        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060;Branch=test").unwrap();
521        let entry = &via.entries()[0];
522        assert_eq!(entry.param("branch"), Some(Some("test")));
523        assert_eq!(entry.param("BRANCH"), Some(Some("test")));
524    }
525
526    #[test]
527    fn test_display_ipv6() {
528        let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1]:5060").unwrap();
529        let displayed = via.to_string();
530        assert!(displayed.contains("[2001:db8::1]"));
531    }
532
533    #[test]
534    fn from_entries_matches_parse() {
535        let a = "SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bK1";
536        let b = "SIP/2.0/TCP 203.0.113.5";
537        let split = SipVia::from_entries([a, b]).unwrap();
538        let joined = SipVia::parse(&format!("{a}, {b}")).unwrap();
539        assert_eq!(split, joined);
540        assert_eq!(split.to_string(), format!("{a}, {b}"));
541    }
542
543    #[test]
544    fn from_entries_bad_entry_is_error() {
545        assert!(matches!(
546            SipVia::from_entries(["SIP/2.0/UDP 198.51.100.1", "invalid"]),
547            Err(SipViaError::InvalidFormat(_))
548        ));
549    }
550
551    #[test]
552    fn sws_around_protocol_slashes() {
553        let via = SipVia::parse("SIP / 2.0 / UDP example.com").unwrap();
554        let entry = &via.entries()[0];
555        assert_eq!(entry.protocol(), "SIP");
556        assert_eq!(entry.version(), "2.0");
557        assert_eq!(entry.transport(), "UDP");
558        assert_eq!(entry.host(), "example.com");
559        assert_eq!(entry.port(), None);
560    }
561
562    #[test]
563    fn sws_around_sent_by_colon() {
564        let via = SipVia::parse("SIP/2.0/UDP example.com : 5060;branch=z9hG4bK1").unwrap();
565        let entry = &via.entries()[0];
566        assert_eq!(entry.host(), "example.com");
567        assert_eq!(entry.port(), Some(5060));
568        assert_eq!(entry.branch(), Some("z9hG4bK1"));
569    }
570
571    #[test]
572    fn sws_around_ipv6_reference_colon() {
573        let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1] : 5060").unwrap();
574        let entry = &via.entries()[0];
575        assert_eq!(entry.host(), "2001:db8::1");
576        assert_eq!(entry.port(), Some(5060));
577    }
578
579    #[test]
580    fn empty_transport_before_sent_by_accepted() {
581        let via = SipVia::parse("SIP/2.0/ example.com:5060").unwrap();
582        let entry = &via.entries()[0];
583        assert_eq!(entry.transport(), "");
584        assert_eq!(entry.host(), "example.com");
585        assert_eq!(entry.port(), Some(5060));
586    }
587
588    #[test]
589    fn junk_after_sent_by_is_error() {
590        assert!(SipVia::parse("SIP/2.0/UDP example.com extra").is_err());
591        assert!(SipVia::parse("SIP/2.0/UDP/X example.com").is_err());
592        assert!(SipVia::parse("SIP/2.0/UDP").is_err());
593    }
594
595    #[test]
596    fn unbracketed_ipv6_is_error() {
597        assert!(matches!(
598            SipVia::parse("SIP/2.0/UDP 2001:db8::1:5060"),
599            Err(SipViaError::InvalidFormat(_))
600        ));
601        assert!(SipVia::parse("SIP/2.0/UDP 2001:db8::1").is_err());
602    }
603
604    #[test]
605    fn params_keep_trimmed_raw_values() {
606        let via = SipVia::parse("SIP/2.0/UDP example.com ; Branch = z9hG4bK1 ; rport ; x=\"a;b\"")
607            .unwrap();
608        let entry = &via.entries()[0];
609        assert_eq!(
610            entry.params(),
611            &[
612                ("branch".to_string(), Some("z9hG4bK1".to_string())),
613                ("rport".to_string(), None),
614                ("x".to_string(), Some("\"a;b\"".to_string())),
615            ]
616        );
617        assert_eq!(entry.rport(), Some(None));
618        assert_eq!(
619            via.to_string(),
620            "SIP/2.0/UDP example.com;branch=z9hG4bK1;rport;x=\"a;b\""
621        );
622    }
623
624    #[test]
625    fn error_display_omits_input() {
626        for raw in [
627            "SIP/2.0/UDP secret.example.com extra",
628            "SIP/2.0/UDP secret.example.com:99999",
629            "SIP/2.0/UDP [2001:db8::1]secret",
630            "SIP/2.0/UDP example.com;rport=secret",
631            "secret",
632        ] {
633            let err = SipVia::parse(raw).unwrap_err();
634            assert!(!err
635                .to_string()
636                .contains("secret"));
637        }
638    }
639
640    #[test]
641    fn from_entries_empty_is_empty_error() {
642        assert!(matches!(
643            SipVia::from_entries(std::iter::empty::<&str>()),
644            Err(SipViaError::Empty)
645        ));
646    }
647}