Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/common/uri/param/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,13 @@ pub mod tokenizer {

let (rem, (_, name, value)) = tuple((
tag(";"),
take_while(I::is_token), //rfc3261 includes other chars as well, needs fixing..
take_while(I::is_token),
opt(map(
tuple((
tag("="),
alt((
recognize(delimited(tag("\""), take_until("\""), tag("\""))),
take_while(I::is_token),
take_while(I::is_param_value),
)),
)),
|t| t.1,
Expand Down
11 changes: 11 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@
pub use crate::common::uri::*;
pub use crate::common::*;

pub use crate::message::header_macros::*;

Check warning on line 276 in src/lib.rs

View workflow job for this annotation

GitHub Actions / Check

unused import: `crate::message::header_macros::*`

Check warning on line 276 in src/lib.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused import: `crate::message::header_macros::*`

pub mod typed {
pub use crate::headers::typed::*;
Expand Down Expand Up @@ -317,6 +317,9 @@
fn is_alphabetic(c: I) -> bool;
fn is_alphanumeric(c: I) -> bool;
fn is_token(c: I) -> bool;
/// RFC 3261 §25.1: paramchar allows param-unreserved characters in addition to token chars.
/// param-unreserved = "[" / "]" / "/" / ":" / "&" / "+" / "$"
fn is_param_value(c: I) -> bool;
}

impl<'a> AbstractInput<'a, char> for &'a str {
Expand All @@ -337,6 +340,10 @@
fn is_token(c: char) -> bool {
Self::is_alphanumeric(c) || "-.!%*_+`'~".contains(c)
}

fn is_param_value(c: char) -> bool {
Self::is_token(c) || "[]/:&+$".contains(c)
}
}

impl<'a> AbstractInput<'a, u8> for &'a [u8] {
Expand All @@ -358,6 +365,10 @@

is_alphanumeric(c) || "-.!%*_+`'~".contains(char::from(c))
}

fn is_param_value(c: u8) -> bool {
Self::is_token(c) || b"[]/:&+$".contains(&c)
}
}

pub(crate) mod utils {
Expand Down
58 changes: 58 additions & 0 deletions tests/headers/record_route/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,64 @@ mod display {
}
}

mod param_values_with_colons {
use super::*;
use rsip::common::uri::param::{OtherParam, OtherParamValue};

/// Kamailio encodes internal routing info in Record-Route URI parameters
/// using values that contain SIP URIs (e.g. du=sip:host:port).
/// The colon must be preserved per RFC 3261 §25.1 (param-unreserved).
#[test]
fn kamailio_du_param_round_trip() -> Result<(), rsip::Error> {
let input = "<sip:82.202.218.130;lr=on;ftag=d4nwJ0jF;du=sip:95.143.188.49:5060;did=893.d6d1>";
let tokenizer = UriWithParamsListTokenizer::tokenize(input).unwrap().1;
let record_route: RecordRoute = tokenizer.try_into()?;
let uris = record_route.uris();

assert_eq!(uris.len(), 1);
let uri = &uris[0];

// Verify all params are preserved, including du with colons
assert_eq!(
uri.uri.params,
vec![
Param::Other(OtherParam::from("lr"), Some(OtherParamValue::from("on"))),
Param::Other(OtherParam::from("ftag"), Some(OtherParamValue::from("d4nwJ0jF"))),
Param::Other(
OtherParam::from("du"),
Some(OtherParamValue::from("sip:95.143.188.49:5060"))
),
Param::Other(OtherParam::from("did"), Some(OtherParamValue::from("893.d6d1"))),
]
);

// Verify round-trip preserves the full value
// UriWithParams::Display already wraps in angle brackets
let output = uri.to_string();
assert_eq!(output, input);

Ok(())
}

#[test]
fn param_value_with_slashes_and_colons() -> Result<(), rsip::Error> {
let input = "<sip:proxy.example.com;lr;dest=sip:10.0.0.1:5080/udp>";
let tokenizer = UriWithParamsListTokenizer::tokenize(input).unwrap().1;
let record_route: RecordRoute = tokenizer.try_into()?;
let uri = &record_route.uris()[0];

let dest_param = uri.uri.params.iter().find(|p| {
matches!(p, Param::Other(name, _) if name.to_string() == "dest")
});
assert!(dest_param.is_some());
if let Some(Param::Other(_, Some(val))) = dest_param {
assert_eq!(val.to_string(), "sip:10.0.0.1:5080/udp");
}

Ok(())
}
}

mod try_from_tokenizer {
use super::*;

Expand Down
Loading