Files
siprouter/rust/crates/sip-proto/src/rewrite.rs

131 lines
4.3 KiB
Rust

//! SIP URI and SDP body rewriting helpers.
//!
//! Ported from ts/sip/rewrite.ts.
use crate::Endpoint;
/// Replaces the host:port in every `sip:` / `sips:` URI found in `value`.
pub fn rewrite_sip_uri(value: &str, host: &str, port: u16) -> String {
let mut result = String::with_capacity(value.len());
let mut i = 0;
let bytes = value.as_bytes();
while i < bytes.len() {
// Look for "sip:" or "sips:"
let scheme_len = if i + 4 <= bytes.len()
&& (bytes[i..].starts_with(b"sip:") || bytes[i..].starts_with(b"SIP:"))
{
4
} else if i + 5 <= bytes.len()
&& (bytes[i..].starts_with(b"sips:") || bytes[i..].starts_with(b"SIPS:"))
{
5
} else {
result.push(value[i..].chars().next().unwrap());
i += value[i..].chars().next().unwrap().len_utf8();
continue;
};
let scheme = &value[i..i + scheme_len];
let rest = &value[i + scheme_len..];
// Check for userpart (contains '@')
let (userpart, host_start) = if let Some(at) = rest.find('@') {
// Make sure @ comes before any delimiters
let delim = rest.find(|c: char| c == '>' || c == ';' || c == ',' || c.is_whitespace());
if delim.is_none() || at < delim.unwrap() {
(&rest[..=at], at + 1)
} else {
("", 0)
}
} else {
("", 0)
};
// Find the end of the host:port portion
let host_rest = &rest[host_start..];
let end = host_rest
.find(|c: char| c == '>' || c == ';' || c == ',' || c.is_whitespace())
.unwrap_or(host_rest.len());
result.push_str(scheme);
result.push_str(userpart);
result.push_str(&format!("{host}:{port}"));
i += scheme_len + host_start + end;
}
result
}
/// Rewrites the connection address (`c=`) and audio media port (`m=audio`)
/// in an SDP body. Returns the rewritten body together with the original
/// endpoint that was replaced (if any).
pub fn rewrite_sdp(body: &str, ip: &str, port: u16) -> (String, Option<Endpoint>) {
let mut orig_addr: Option<String> = None;
let mut orig_port: Option<u16> = None;
let lines: Vec<String> = body
.replace("\r\n", "\n")
.split('\n')
.map(|line| {
if let Some(rest) = line.strip_prefix("c=IN IP4 ") {
orig_addr = Some(rest.trim().to_string());
format!("c=IN IP4 {ip}")
} else if line.starts_with("m=audio ") {
let parts: Vec<&str> = line.split(' ').collect();
if parts.len() >= 2 {
orig_port = parts[1].parse().ok();
let mut rebuilt = parts[0].to_string();
rebuilt.push(' ');
rebuilt.push_str(&port.to_string());
for part in &parts[2..] {
rebuilt.push(' ');
rebuilt.push_str(part);
}
return rebuilt;
}
line.to_string()
} else {
line.to_string()
}
})
.collect();
let original = match (orig_addr, orig_port) {
(Some(a), Some(p)) => Some(Endpoint { address: a, port: p }),
_ => None,
};
(lines.join("\r\n"), original)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rewrite_sip_uri() {
let input = "<sip:user@10.0.0.1:5060>";
let result = rewrite_sip_uri(input, "192.168.1.1", 5070);
assert_eq!(result, "<sip:user@192.168.1.1:5070>");
}
#[test]
fn test_rewrite_sip_uri_no_port() {
let input = "sip:user@10.0.0.1";
let result = rewrite_sip_uri(input, "192.168.1.1", 5070);
assert_eq!(result, "sip:user@192.168.1.1:5070");
}
#[test]
fn test_rewrite_sdp() {
let sdp = "v=0\r\nc=IN IP4 10.0.0.1\r\nm=audio 5060 RTP/AVP 0 9\r\na=sendrecv\r\n";
let (rewritten, orig) = rewrite_sdp(sdp, "192.168.1.1", 20000);
assert!(rewritten.contains("c=IN IP4 192.168.1.1"));
assert!(rewritten.contains("m=audio 20000 RTP/AVP 0 9"));
let ep = orig.unwrap();
assert_eq!(ep.address, "10.0.0.1");
assert_eq!(ep.port, 5060);
}
}