#[macro_use]
mod macros;
pub use sip_uri;
pub mod accept;
pub mod accept_encoding;
pub mod accept_language;
pub mod auth;
#[cfg(feature = "conference-info")]
pub mod conference_info;
pub mod contact;
pub mod geolocation;
pub mod header;
pub mod header_addr;
pub mod history_info;
#[cfg(feature = "message")]
pub mod message;
pub mod security;
pub mod uri_info;
pub mod via;
pub mod warning;
pub use accept::{SipAccept, SipAcceptEntry, SipAcceptError};
pub use accept_encoding::{SipAcceptEncoding, SipAcceptEncodingEntry, SipAcceptEncodingError};
pub use accept_language::{SipAcceptLanguage, SipAcceptLanguageEntry, SipAcceptLanguageError};
pub use auth::{SipAuthError, SipAuthValue};
pub use contact::ContactValue;
pub use geolocation::{SipGeolocation, SipGeolocationRef};
pub use header::{ParseSipHeaderError, SipHeader, SipHeaderLookup};
pub use header_addr::{ParseSipHeaderAddrError, SipHeaderAddr};
pub use history_info::{HistoryInfo, HistoryInfoEntry, HistoryInfoError, HistoryInfoReason};
#[cfg(feature = "message")]
pub use message::{extract_header, extract_request_uri};
pub use security::{SipSecurity, SipSecurityError, SipSecurityMechanism};
pub use uri_info::{UriInfo, UriInfoEntry, UriInfoError};
pub use via::{SipVia, SipViaEntry, SipViaError};
pub use warning::{SipWarning, SipWarningEntry, SipWarningError};
pub(crate) fn fmt_joined<T: std::fmt::Display>(
f: &mut std::fmt::Formatter<'_>,
items: &[T],
separator: &str,
) -> std::fmt::Result {
for (i, item) in items
.iter()
.enumerate()
{
if i > 0 {
f.write_str(separator)?;
}
write!(f, "{item}")?;
}
Ok(())
}
pub(crate) fn unescape_quoted_pair(s: &str) -> String {
if !s.contains('\\') {
return s.to_string();
}
let mut result = String::with_capacity(s.len());
let mut escaped = false;
for ch in s.chars() {
if escaped {
result.push(ch);
escaped = false;
} else if ch == '\\' {
escaped = true;
} else {
result.push(ch);
}
}
result
}
pub(crate) fn escape_quoted_pair(s: &str) -> String {
if !s.contains(['"', '\\']) {
return s.to_string();
}
let mut result = String::with_capacity(s.len() + 4);
for ch in s.chars() {
if ch == '"' || ch == '\\' {
result.push('\\');
}
result.push(ch);
}
result
}
pub(crate) fn write_quoted_pair(f: &mut std::fmt::Formatter<'_>, value: &str) -> std::fmt::Result {
f.write_str("\"")?;
for ch in value.chars() {
if ch == '"' || ch == '\\' {
write!(f, "\\{ch}")?;
} else {
write!(f, "{ch}")?;
}
}
f.write_str("\"")
}
pub fn split_comma_entries(raw: &str) -> Vec<&str> {
let mut entries = Vec::new();
let mut depth = 0u32;
let mut in_quotes = false;
let mut prev_backslash = false;
let mut start = 0;
for (i, ch) in raw.char_indices() {
if prev_backslash {
prev_backslash = false;
continue;
}
match ch {
'\\' if in_quotes => prev_backslash = true,
'"' => in_quotes = !in_quotes,
'<' if !in_quotes => depth += 1,
'>' if !in_quotes => depth = depth.saturating_sub(1),
',' if depth == 0 && !in_quotes => {
entries.push(&raw[start..i]);
start = i + 1;
}
_ => {}
}
}
if start < raw.len() {
entries.push(&raw[start..]);
}
entries
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_comma_simple() {
assert_eq!(split_comma_entries("a, b, c"), vec!["a", " b", " c"]);
}
#[test]
fn split_comma_respects_angle_brackets() {
let input = "<sip:a@host,x>, <sip:b@host>";
let parts = split_comma_entries(input);
assert_eq!(parts.len(), 2);
assert!(parts[0].contains("host,x"));
}
#[test]
fn split_comma_respects_quoted_strings() {
let input = r#"301 example.com "text, comma", 399 example.org "ok""#;
let parts = split_comma_entries(input);
assert_eq!(parts.len(), 2);
assert!(parts[0].contains("text, comma"));
}
#[test]
fn split_comma_respects_escaped_quote() {
let input = r#"301 example.com "say \"hi, there\"", 399 example.org "ok""#;
let parts = split_comma_entries(input);
assert_eq!(parts.len(), 2);
}
}