@@ -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+
143276fn 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