1#[macro_use]
27mod macros;
28
29pub use sip_uri;
30
31pub mod accept;
32pub mod accept_encoding;
33pub mod accept_language;
34pub mod auth;
35#[cfg(feature = "conference-info")]
36pub mod conference_info;
37pub mod contact;
38pub mod geolocation;
39pub mod header;
40pub mod header_addr;
41pub mod history_info;
42#[cfg(feature = "message")]
43pub mod message;
44pub mod security;
45pub mod uri_info;
46pub mod via;
47pub mod warning;
48
49pub use accept::{SipAccept, SipAcceptEntry, SipAcceptError};
50pub use accept_encoding::{SipAcceptEncoding, SipAcceptEncodingEntry, SipAcceptEncodingError};
51pub use accept_language::{SipAcceptLanguage, SipAcceptLanguageEntry, SipAcceptLanguageError};
52pub use auth::{SipAuthError, SipAuthValue};
53pub use contact::ContactValue;
54pub use geolocation::{SipGeolocation, SipGeolocationRef};
55pub use header::{ParseSipHeaderError, SipHeader, SipHeaderLookup};
56pub use header_addr::{ParseSipHeaderAddrError, SipHeaderAddr};
57pub use history_info::{HistoryInfo, HistoryInfoEntry, HistoryInfoError, HistoryInfoReason};
58#[cfg(feature = "message")]
59pub use message::{extract_header, extract_request_uri};
60pub use security::{SipSecurity, SipSecurityError, SipSecurityMechanism};
61pub use uri_info::{UriInfo, UriInfoEntry, UriInfoError};
62pub use via::{SipVia, SipViaEntry, SipViaError};
63pub use warning::{SipWarning, SipWarningEntry, SipWarningError};
64
65pub(crate) fn fmt_joined<T: std::fmt::Display>(
67 f: &mut std::fmt::Formatter<'_>,
68 items: &[T],
69 separator: &str,
70) -> std::fmt::Result {
71 for (i, item) in items
72 .iter()
73 .enumerate()
74 {
75 if i > 0 {
76 f.write_str(separator)?;
77 }
78 write!(f, "{item}")?;
79 }
80 Ok(())
81}
82
83pub(crate) fn unescape_quoted_pair(s: &str) -> String {
88 if !s.contains('\\') {
89 return s.to_string();
90 }
91 let mut result = String::with_capacity(s.len());
92 let mut escaped = false;
93 for ch in s.chars() {
94 if escaped {
95 result.push(ch);
96 escaped = false;
97 } else if ch == '\\' {
98 escaped = true;
99 } else {
100 result.push(ch);
101 }
102 }
103 result
104}
105
106pub(crate) fn escape_quoted_pair(s: &str) -> String {
110 if !s.contains(['"', '\\']) {
111 return s.to_string();
112 }
113 let mut result = String::with_capacity(s.len() + 4);
114 for ch in s.chars() {
115 if ch == '"' || ch == '\\' {
116 result.push('\\');
117 }
118 result.push(ch);
119 }
120 result
121}
122
123pub(crate) fn write_quoted_pair(f: &mut std::fmt::Formatter<'_>, value: &str) -> std::fmt::Result {
126 f.write_str("\"")?;
127 for ch in value.chars() {
128 if ch == '"' || ch == '\\' {
129 write!(f, "\\{ch}")?;
130 } else {
131 write!(f, "{ch}")?;
132 }
133 }
134 f.write_str("\"")
135}
136
137pub fn split_comma_entries(raw: &str) -> Vec<&str> {
148 let mut entries = Vec::new();
149 let mut depth = 0u32;
150 let mut in_quotes = false;
151 let mut prev_backslash = false;
152 let mut start = 0;
153
154 for (i, ch) in raw.char_indices() {
155 if prev_backslash {
156 prev_backslash = false;
157 continue;
158 }
159 match ch {
160 '\\' if in_quotes => prev_backslash = true,
161 '"' => in_quotes = !in_quotes,
162 '<' if !in_quotes => depth += 1,
163 '>' if !in_quotes => depth = depth.saturating_sub(1),
164 ',' if depth == 0 && !in_quotes => {
165 entries.push(&raw[start..i]);
166 start = i + 1;
167 }
168 _ => {}
169 }
170 }
171 if start < raw.len() {
172 entries.push(&raw[start..]);
173 }
174
175 entries
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn split_comma_simple() {
184 assert_eq!(split_comma_entries("a, b, c"), vec!["a", " b", " c"]);
185 }
186
187 #[test]
188 fn split_comma_respects_angle_brackets() {
189 let input = "<sip:a@host,x>, <sip:b@host>";
190 let parts = split_comma_entries(input);
191 assert_eq!(parts.len(), 2);
192 assert!(parts[0].contains("host,x"));
193 }
194
195 #[test]
196 fn split_comma_respects_quoted_strings() {
197 let input = r#"301 example.com "text, comma", 399 example.org "ok""#;
198 let parts = split_comma_entries(input);
199 assert_eq!(parts.len(), 2);
200 assert!(parts[0].contains("text, comma"));
201 }
202
203 #[test]
204 fn split_comma_respects_escaped_quote() {
205 let input = r#"301 example.com "say \"hi, there\"", 399 example.org "ok""#;
206 let parts = split_comma_entries(input);
207 assert_eq!(parts.len(), 2);
208 }
209}