Skip to content

Commit 1dda6bb

Browse files
committed
bump version to 0.2.72 and enhance URI extraction logic in contact headers
1 parent d9f0ac9 commit 1dda6bb

3 files changed

Lines changed: 155 additions & 9 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rsipstack"
3-
version = "0.2.71"
3+
version = "0.2.72"
44
edition = "2021"
55
description = "SIP Stack Rust library for building SIP applications"
66
license = "MIT"
@@ -39,6 +39,7 @@ webpki-roots = { version = "1.0.2", optional = true }
3939
rustls = "0.23.31"
4040
clap = { version = "4.5.47", features = ["derive"] }
4141
http = "1.3.1"
42+
nom = "7.1.3"
4243

4344
[features]
4445
default = ["rustls", "websocket"]

src/rsip_ext.rs

Lines changed: 144 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,45 @@ macro_rules! header_pop {
7474
}
7575

7676
pub fn extract_uri_from_contact(line: &str) -> Result<rsip::Uri> {
77-
if let Ok(mut uri) = rsip::headers::Contact::from(line).uri() {
78-
uri.params
79-
.retain(|p| matches!(p, rsip::Param::Transport(_)));
77+
if let Ok(uri) = rsip::headers::Contact::from(line).uri() {
8078
return Ok(uri);
8179
}
8280

83-
match line.split('<').nth(1).and_then(|s| s.split('>').next()) {
84-
Some(uri) => rsip::Uri::try_from(uri).map_err(Into::into),
85-
None => Err(Error::Error("no uri found".to_string())),
81+
let tokenizer = CustomContactTokenizer::from_str(line)?;
82+
let mut uri = rsip::Uri::try_from(tokenizer.uri()).map_err(Error::from)?;
83+
uri.params.retain(|p| {
84+
if let rsip::Param::Transport(rsip::Transport::Udp) = p {
85+
false
86+
} else {
87+
true
88+
}
89+
});
90+
apply_tokenizer_params(&mut uri, &tokenizer);
91+
return Ok(uri);
92+
}
93+
94+
fn apply_tokenizer_params(uri: &mut rsip::Uri, tokenizer: &CustomContactTokenizer) {
95+
for (name, value) in tokenizer.params.iter().map(|p| (p.name, p.value)) {
96+
if name.eq_ignore_ascii_case("transport") {
97+
continue;
98+
}
99+
let mut updated = false;
100+
for param in uri.params.iter_mut() {
101+
if let rsip::Param::Other(key, existing_value) = param {
102+
if key.value().eq_ignore_ascii_case(name) {
103+
*existing_value =
104+
value.map(|v| rsip::param::OtherParamValue::new(v.to_string()));
105+
updated = true;
106+
break;
107+
}
108+
}
109+
}
110+
if !updated {
111+
uri.params.push(rsip::Param::Other(
112+
rsip::param::OtherParam::new(name),
113+
value.map(|v| rsip::param::OtherParamValue::new(v.to_string())),
114+
));
115+
}
86116
}
87117
}
88118

@@ -104,6 +134,114 @@ pub fn destination_from_request(request: &rsip::Request) -> Option<SipAddr> {
104134
.or_else(|| SipAddr::try_from(&request.uri).ok())
105135
}
106136

137+
use nom::{
138+
branch::alt,
139+
bytes::complete::{is_not, take_until},
140+
character::complete::{char, multispace0},
141+
combinator::{map, opt, rest},
142+
multi::separated_list0,
143+
sequence::{delimited, preceded},
144+
IResult,
145+
};
146+
147+
#[derive(Debug)]
148+
pub(crate) struct CustomContactTokenizer<'a> {
149+
uri: &'a str,
150+
params: Vec<CustomContactParamToken<'a>>,
151+
}
152+
153+
#[derive(Debug)]
154+
struct CustomContactParamToken<'a> {
155+
name: &'a str,
156+
value: Option<&'a str>,
157+
}
158+
159+
impl<'a> CustomContactTokenizer<'a> {
160+
pub(crate) fn from_str(input: &'a str) -> super::Result<Self> {
161+
let trimmed = input.trim();
162+
if trimmed.is_empty() {
163+
return Err(Error::Error("empty contact header".into()));
164+
}
165+
166+
match custom_contact_tokenize(trimmed) {
167+
Ok((_rem, tokenizer)) => Ok(tokenizer),
168+
Err(_) => Ok(Self::from_plain(trimmed)),
169+
}
170+
}
171+
172+
fn from_plain(uri: &'a str) -> Self {
173+
Self {
174+
uri,
175+
params: custom_contact_parse_params(uri),
176+
}
177+
}
178+
179+
pub(crate) fn uri(&self) -> &'a str {
180+
self.uri
181+
}
182+
}
183+
184+
fn custom_contact_tokenize<'a>(input: &'a str) -> IResult<&'a str, CustomContactTokenizer<'a>> {
185+
alt((
186+
custom_contact_with_brackets,
187+
custom_contact_without_brackets,
188+
))(input)
189+
}
190+
191+
fn custom_contact_with_brackets<'a>(
192+
input: &'a str,
193+
) -> IResult<&'a str, CustomContactTokenizer<'a>> {
194+
let (input, _) = multispace0(input)?;
195+
let (input, _) = opt(take_until("<"))(input)?;
196+
let (input, _) = char('<')(input)?;
197+
let (input, uri) = take_until(">")(input)?;
198+
let (input, _) = char('>')(input)?;
199+
200+
let uri = uri.trim();
201+
let params = custom_contact_parse_params(uri);
202+
203+
Ok((input, CustomContactTokenizer { uri, params }))
204+
}
205+
206+
fn custom_contact_without_brackets<'a>(
207+
input: &'a str,
208+
) -> IResult<&'a str, CustomContactTokenizer<'a>> {
209+
let (input, uri) = map(rest, |s: &str| s.trim())(input)?;
210+
let params = custom_contact_parse_params(uri);
211+
Ok((input, CustomContactTokenizer { uri, params }))
212+
}
213+
214+
fn custom_contact_parse_params<'a>(uri: &'a str) -> Vec<CustomContactParamToken<'a>> {
215+
let path = uri.split_once('?').map_or(uri, |(path, _)| path);
216+
if let Some(idx) = path.find(';') {
217+
let params_str = &path[idx + 1..];
218+
if params_str.is_empty() {
219+
return Vec::new();
220+
}
221+
222+
match separated_list0(char(';'), custom_contact_param)(params_str) {
223+
Ok((_, params)) => params.into_iter().filter(|p| !p.name.is_empty()).collect(),
224+
Err(_) => Vec::new(),
225+
}
226+
} else {
227+
Vec::new()
228+
}
229+
}
230+
231+
fn custom_contact_param<'a>(input: &'a str) -> IResult<&'a str, CustomContactParamToken<'a>> {
232+
let (input, _) = multispace0(input)?;
233+
let (input, name) = map(is_not("=; \t\r\n?"), |v: &str| v.trim())(input)?;
234+
let (input, value) = opt(preceded(
235+
char('='),
236+
alt((
237+
delimited(char('"'), take_until("\""), char('"')),
238+
map(is_not("; \t\r\n?"), |v: &str| v.trim()),
239+
)),
240+
))(input)?;
241+
242+
Ok((input, CustomContactParamToken { name, value }))
243+
}
244+
107245
#[test]
108246
fn test_rsip_headers_ext() {
109247
use rsip::{Header, Headers};

src/transaction/tests/mod.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,22 @@ mod tests {
4242

4343
#[test]
4444
fn test_linphone_contact() {
45+
let line = "sip:bob@localhost;transport=udp";
46+
let contact_uri = extract_uri_from_contact(line).expect("failed to parse contact");
47+
assert_eq!(contact_uri.to_string(), "sip:bob@localhost");
48+
4549
let line = "<sip:bob@localhost;transport=udp>;expires=3600;+org.linphone.specs=\"lime\"";
4650
let contact_uri = extract_uri_from_contact(line).expect("failed to parse contact");
47-
assert_eq!(contact_uri.to_string(), "sip:bob@localhost;transport=UDP");
51+
assert_eq!(contact_uri.to_string(), "sip:bob@localhost");
4852

4953
let line = "<sip:bob@restsend.com;transport=udp>;message-expires=2419200;+sip.instance=\"<urn:uuid:12345-81fa-4fe3-aa6c-17bffdbcf619>\"";
5054
let contact_uri = extract_uri_from_contact(line).expect("failed to parse contact");
55+
assert_eq!(contact_uri.to_string(), "sip:bob@restsend.com");
56+
let line = "<sip:linphone@domain.com;gr=urn:uuid:c8651907-f0b2-0027-bdbd-ce4ed05c9ef4>;+org.linphone.specs=\"ephemeral/1.1,groupchat/1.1,lime\"";
57+
let contact_uri = extract_uri_from_contact(line).expect("failed to parse contact");
5158
assert_eq!(
5259
contact_uri.to_string(),
53-
"sip:bob@restsend.com;transport=UDP"
60+
"sip:linphone@domain.com;gr=urn:uuid:c8651907-f0b2-0027-bdbd-ce4ed05c9ef4"
5461
);
5562
}
5663
}

0 commit comments

Comments
 (0)