sip_header/lib.rs
1//! SIP header field parsers for standard RFC types.
2//!
3//! This crate provides parsers for SIP header values as defined in RFC 3261
4//! and extensions. It sits between URI parsing ([`sip_uri`]) and full SIP
5//! stacks, handling the header-level grammar: display names, header parameters,
6//! and structured header values like Call-Info and History-Info.
7//!
8//! # Modules
9//!
10//! - [`header_addr`] — RFC 3261 `name-addr` with header-level parameters
11//! - [`header`] — SIP header name catalog and [`SipHeaderLookup`] trait
12//! - [`message`] — Extract headers from raw SIP message text
13//! - [`call_info`] — RFC 3261 §20.9 Call-Info header parser
14//! - [`history_info`] — RFC 7044 History-Info header parser
15//! - [`geolocation`] — RFC 6442 Geolocation header parser
16//! - `conference_info` — RFC 4575 conference event package (feature: `conference-info`)
17
18#[macro_use]
19mod macros;
20
21pub use sip_uri;
22
23pub mod call_info;
24#[cfg(feature = "conference-info")]
25pub mod conference_info;
26pub mod geolocation;
27pub mod header;
28pub mod header_addr;
29pub mod history_info;
30pub mod message;
31
32pub use call_info::{SipCallInfo, SipCallInfoEntry, SipCallInfoError};
33pub use geolocation::{SipGeolocation, SipGeolocationRef};
34pub use header::{ParseSipHeaderError, SipHeader, SipHeaderLookup};
35pub use header_addr::{ParseSipHeaderAddrError, SipHeaderAddr};
36pub use history_info::{HistoryInfo, HistoryInfoEntry, HistoryInfoError, HistoryInfoReason};
37pub use message::extract_header;
38
39/// Format a slice of displayable items as a separated list.
40pub(crate) fn fmt_joined<T: std::fmt::Display>(
41 f: &mut std::fmt::Formatter<'_>,
42 items: &[T],
43 separator: &str,
44) -> std::fmt::Result {
45 for (i, item) in items
46 .iter()
47 .enumerate()
48 {
49 if i > 0 {
50 f.write_str(separator)?;
51 }
52 write!(f, "{item}")?;
53 }
54 Ok(())
55}
56
57/// Split comma-separated header entries respecting angle-bracket nesting.
58///
59/// SIP headers that carry lists (RFC 3261 §7.3.1) use commas as delimiters,
60/// but commas may also appear inside angle-bracketed URIs. This function
61/// splits only on commas at bracket depth zero.
62pub(crate) fn split_comma_entries(raw: &str) -> Vec<&str> {
63 let mut entries = Vec::new();
64 let mut depth = 0u32;
65 let mut start = 0;
66
67 for (i, ch) in raw.char_indices() {
68 match ch {
69 '<' => depth += 1,
70 '>' => depth = depth.saturating_sub(1),
71 ',' if depth == 0 => {
72 entries.push(&raw[start..i]);
73 start = i + 1;
74 }
75 _ => {}
76 }
77 }
78 if start < raw.len() {
79 entries.push(&raw[start..]);
80 }
81
82 entries
83}