Skip to content

Commit 43a55af

Browse files
committed
fix: register contact parseing
1 parent e7e5fac commit 43a55af

2 files changed

Lines changed: 195 additions & 14 deletions

File tree

src/dialog/registration.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,8 @@ use crate::{
1313
transport::SipAddr,
1414
Result,
1515
};
16-
use rsip::{
17-
prelude::{HeadersExt, ToTypedHeader},
18-
Param, Response, SipMessage, StatusCode,
19-
};
20-
use tracing::debug;
16+
use rsip::{Param, Response, SipMessage, StatusCode};
17+
use tracing::{debug, warn};
2118

2219
/// SIP Registration Client
2320
///
@@ -469,11 +466,15 @@ impl Registration {
469466
let received = resp.via_received();
470467
// Update contact header from response
471468

472-
match resp.contact_header() {
469+
match resp.typed_contact_headers() {
473470
Ok(contact) => {
474-
self.contact = contact.typed().ok();
471+
if let Some(contact) = contact.first().cloned() {
472+
self.contact = Some(contact);
473+
}
474+
}
475+
Err(e) => {
476+
warn!("failed to parse contact: {:?}", e);
475477
}
476-
Err(_) => {}
477478
};
478479
if self.public_address != received {
479480
debug!(

src/rsip_ext.rs

Lines changed: 186 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub trait RsipResponseExt {
2222
fn reason_phrase(&self) -> Option<&str>;
2323
fn via_received(&self) -> Option<rsip::HostWithPort>;
2424
fn content_type(&self) -> Option<rsip::headers::ContentType>;
25+
fn typed_contact_headers(&self) -> Result<Vec<rsip::typed::Contact>>;
2526
fn contact_uri(&self) -> Result<rsip::Uri>;
2627
fn remote_uri(&self, destination: Option<&SipAddr>) -> Result<rsip::Uri>;
2728
}
@@ -61,14 +62,20 @@ impl RsipResponseExt for rsip::Response {
6162
None
6263
}
6364

65+
fn typed_contact_headers(&self) -> Result<Vec<rsip::typed::Contact>> {
66+
let contact = match self.contact_header() {
67+
Ok(contact) => contact,
68+
Err(rsip::Error::MissingHeader(_)) => return Ok(Vec::new()),
69+
Err(e) => return Err(Error::from(e)),
70+
};
71+
parse_typed_contact_header_list(contact.value())
72+
}
73+
6474
fn contact_uri(&self) -> Result<rsip::Uri> {
65-
let contact = self.contact_header()?;
66-
if let Ok(typed_contact) = contact.typed() {
67-
Ok(typed_contact.uri)
75+
if let Some(contact) = self.typed_contact_headers()?.first() {
76+
Ok(contact.uri.clone())
6877
} else {
69-
let mut uri = extract_uri_from_contact(contact.value())?;
70-
uri.headers.clear();
71-
Ok(uri)
78+
Err(Error::Error("missing Contact header".to_string()))
7279
}
7380
}
7481

@@ -140,6 +147,132 @@ pub fn extract_uri_from_contact(line: &str) -> Result<rsip::Uri> {
140147
return Ok(uri);
141148
}
142149

150+
pub fn parse_typed_contact_header_list(line: &str) -> Result<Vec<rsip::typed::Contact>> {
151+
let trimmed = line.trim();
152+
if trimmed.is_empty() {
153+
return Err(Error::Error("empty Contact header".to_string()));
154+
}
155+
156+
let values = split_contact_header_values(trimmed)?;
157+
let mut contacts = Vec::with_capacity(values.len());
158+
for value in values {
159+
contacts.push(parse_typed_contact(value.as_str())?);
160+
}
161+
162+
Ok(contacts)
163+
}
164+
165+
pub fn parse_typed_contact(line: &str) -> Result<rsip::typed::Contact> {
166+
if let Ok(contact) = rsip::headers::Contact::from(line).typed() {
167+
return Ok(contact);
168+
}
169+
170+
let trimmed = line.trim();
171+
if trimmed.is_empty() {
172+
return Err(Error::Error("empty Contact header".to_string()));
173+
}
174+
175+
let (display_name, uri_part, header_params_part) = if let Some(start) = trimmed.find('<') {
176+
let end = trimmed[start..]
177+
.find('>')
178+
.map(|offset| start + offset)
179+
.ok_or_else(|| Error::Error("invalid Contact header: missing '>'".to_string()))?;
180+
let display = trimmed[..start].trim();
181+
let display_name = if display.is_empty() {
182+
None
183+
} else {
184+
Some(display.trim_matches('"').to_string())
185+
};
186+
let uri = &trimmed[start + 1..end];
187+
let params = trimmed[end + 1..].trim();
188+
(display_name, uri, params)
189+
} else {
190+
let (uri, params) = split_uri_and_header_params(trimmed);
191+
(None, uri, params)
192+
};
193+
194+
let mut uri = extract_uri_from_contact(uri_part)?;
195+
uri.headers.clear();
196+
197+
let params = parse_contact_header_params(header_params_part)?;
198+
199+
Ok(rsip::typed::Contact {
200+
display_name,
201+
uri,
202+
params,
203+
})
204+
}
205+
206+
pub fn split_contact_header_values(line: &str) -> Result<Vec<String>> {
207+
let mut values = Vec::new();
208+
let mut current = String::new();
209+
let mut in_quotes = false;
210+
let mut angle_depth = 0usize;
211+
212+
for ch in line.chars() {
213+
match ch {
214+
'"' => {
215+
in_quotes = !in_quotes;
216+
current.push(ch);
217+
}
218+
'<' if !in_quotes => {
219+
angle_depth += 1;
220+
current.push(ch);
221+
}
222+
'>' if !in_quotes => {
223+
angle_depth = angle_depth.saturating_sub(1);
224+
current.push(ch);
225+
}
226+
',' if !in_quotes && angle_depth == 0 => {
227+
let value = current.trim();
228+
if !value.is_empty() {
229+
values.push(value.to_string());
230+
}
231+
current.clear();
232+
}
233+
_ => current.push(ch),
234+
}
235+
}
236+
237+
let value = current.trim();
238+
if !value.is_empty() {
239+
values.push(value.to_string());
240+
}
241+
242+
if values.is_empty() {
243+
return Err(Error::Error("empty Contact header".to_string()));
244+
}
245+
246+
Ok(values)
247+
}
248+
249+
fn split_uri_and_header_params(input: &str) -> (&str, &str) {
250+
let path = input.split_once('?').map_or(input, |(path, _)| path);
251+
if let Some(idx) = path.find(';') {
252+
(&input[..idx], &input[idx..])
253+
} else {
254+
(input, "")
255+
}
256+
}
257+
258+
fn parse_contact_header_params(input: &str) -> Result<Vec<rsip::Param>> {
259+
let trimmed = input.trim();
260+
if trimmed.is_empty() {
261+
return Ok(Vec::new());
262+
}
263+
264+
let params = separated_list0(char(';'), custom_contact_param)
265+
.parse(trimmed.trim_start_matches(';'))
266+
.map_err(|_| Error::Error(format!("invalid Contact header params: {}", input)))?
267+
.1;
268+
269+
params
270+
.into_iter()
271+
.filter(|param| !param.name.is_empty())
272+
.map(|param| rsip::Param::try_from((param.name, param.value)).map_err(Error::from))
273+
.collect()
274+
}
275+
143276
fn apply_tokenizer_params(uri: &mut rsip::Uri, tokenizer: &CustomContactTokenizer) {
144277
for (name, value) in tokenizer.params.iter().map(|p| (p.name, p.value)) {
145278
if name.eq_ignore_ascii_case("transport") {
@@ -356,3 +489,50 @@ fn test_rsip_headers_ext() {
356489
]
357490
);
358491
}
492+
493+
#[test]
494+
fn test_parse_typed_contact_headers_from_masked_kamailio_response() {
495+
use rsip::Response;
496+
497+
let response: Response = concat!(
498+
"SIP/2.0 200 OK\r\n",
499+
"Via: SIP/2.0/UDP 192.0.2.10:13050;branch=z9hG4bK-test;rport=60326;received=198.51.100.20\r\n",
500+
"From: <sip:1001@example.com>;tag=from-tag\r\n",
501+
"To: <sip:1001@example.com>;tag=to-tag\r\n",
502+
"CSeq: 1 REGISTER\r\n",
503+
"Call-ID: test-call-id@example.com\r\n",
504+
"Contact: <sip:1001@198.51.100.20:56734;transport=udp>;expires=573;+sip.instance=\"<urn:uuid:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee>\", <sip:1001@192.0.2.10:13050>;expires=3600\r\n",
505+
"Content-Length: 0\r\n",
506+
"\r\n"
507+
)
508+
.try_into()
509+
.expect("failed to parse response");
510+
511+
let contacts = response
512+
.typed_contact_headers()
513+
.expect("failed to parse typed Contact headers");
514+
515+
assert_eq!(contacts.len(), 2);
516+
assert_eq!(
517+
contacts[0].uri.to_string(),
518+
"sip:1001@198.51.100.20:56734"
519+
);
520+
assert_eq!(
521+
contacts[1].uri.to_string(),
522+
"sip:1001@192.0.2.10:13050"
523+
);
524+
assert_eq!(
525+
contacts[0].expires().map(|expires| expires.value()),
526+
Some("573")
527+
);
528+
assert_eq!(
529+
contacts[1].expires().map(|expires| expires.value()),
530+
Some("3600")
531+
);
532+
assert!(contacts[0].params.iter().any(|param| matches!(
533+
param,
534+
rsip::Param::Other(name, Some(value))
535+
if name.value().eq_ignore_ascii_case("+sip.instance")
536+
&& value.value() == "<urn:uuid:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee>"
537+
)));
538+
}

0 commit comments

Comments
 (0)