131 lines
4.3 KiB
Rust
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);
|
||
|
|
}
|
||
|
|
}
|