Skip to content

Commit

Permalink
rust/sip: store multiple header values
Browse files Browse the repository at this point in the history
According to RFC 3261, a single header can be repeated one or more times,
and its name can also be specified using the 'compact form.'

This patch updates the hashmap used for storing headers to accommodate multiple
values instead of just one.

Additionally, if a header name is defined in the compact form, it is expanded
into its long form (i.e., the standard name).

This conversion simplifies the logic for matching a given header
and ensures 1:1 parity with keywords.

Ticket OISF#6374
  • Loading branch information
glongo authored and victorjulien committed Sep 22, 2024
1 parent 969f4d1 commit cfb793c
Showing 1 changed file with 51 additions and 8 deletions.
59 changes: 51 additions & 8 deletions rust/src/sip/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::sdp::parser::{sdp_parse_message, SdpMessage};
use nom7::bytes::streaming::{tag, take, take_while, take_while1};
use nom7::character::streaming::{char, crlf};
use nom7::character::{is_alphabetic, is_alphanumeric, is_digit, is_space};
use nom7::combinator::{map_res, opt};
use nom7::combinator::{map, map_res, opt};
use nom7::sequence::delimited;
use nom7::{Err, IResult, Needed};
use std;
Expand All @@ -38,7 +38,7 @@ pub struct Request {
pub method: String,
pub path: String,
pub version: String,
pub headers: HashMap<String, String>,
pub headers: HashMap<String, Vec<String>>,

pub request_line_len: u16,
pub headers_len: u16,
Expand Down Expand Up @@ -97,6 +97,22 @@ fn is_header_value(b: u8) -> bool {
is_alphanumeric(b) || is_token_char(b) || b"\"#$&(),/;:<=>?@[]{}()^|~\\\t\n\r ".contains(&b)
}

fn expand_header_name(h: &str) -> &str {
match h {
"i" => "Call-ID",
"m" => "Contact",
"e" => "Content-Encoding",
"l" => "Content-Length",
"c" => "Content-Type",
"f" => "From",
"s" => "Subject",
"k" => "Supported",
"t" => "To",
"v" => "Via",
_ => h,
}
}

pub fn sip_parse_request(oi: &[u8]) -> IResult<&[u8], Request> {
let (i, method) = parse_method(oi)?;
let (i, _) = char(' ')(i)?;
Expand Down Expand Up @@ -199,7 +215,7 @@ fn hcolon(i: &[u8]) -> IResult<&[u8], char> {
}

fn message_header(i: &[u8]) -> IResult<&[u8], Header> {
let (i, n) = header_name(i)?;
let (i, n) = map(header_name, expand_header_name)(i)?;
let (i, _) = hcolon(i)?;
let (i, v) = header_value(i)?;
let (i, _) = crlf(i)?;
Expand All @@ -217,8 +233,8 @@ pub fn sip_take_line(i: &[u8]) -> IResult<&[u8], Option<String>> {
Ok((i, Some(line.into())))
}

pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, String>> {
let mut headers_map: HashMap<String, String> = HashMap::new();
pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, Vec<String>>> {
let mut headers_map: HashMap<String, Vec<String>> = HashMap::new();
loop {
match crlf(input) as IResult<&[u8], _> {
Ok((_, _)) => {
Expand All @@ -229,7 +245,10 @@ pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, String>
Err(Err::Incomplete(e)) => return Err(Err::Incomplete(e)),
};
let (rest, header) = message_header(input)?;
headers_map.insert(header.name, header.value);
headers_map
.entry(header.name)
.or_default()
.push(header.value);
input = rest;
}

Expand Down Expand Up @@ -292,7 +311,7 @@ mod tests {
assert_eq!(req.method, "REGISTER");
assert_eq!(req.path, "sip:sip.cybercity.dk");
assert_eq!(req.version, "SIP/2.0");
assert_eq!(req.headers["Content-Length"], "0");
assert_eq!(req.headers["Content-Length"].first().unwrap(), "0");
}

#[test]
Expand All @@ -308,7 +327,7 @@ mod tests {
assert_eq!(req.method, "REGISTER");
assert_eq!(req.path, "sip:sip.cybercity.dk");
assert_eq!(req.version, "SIP/2.0");
assert_eq!(req.headers["Content-Length"], "4");
assert_eq!(req.headers["Content-Length"].first().unwrap(), "4");
assert_eq!(body, "ABCD".as_bytes());
}

Expand Down Expand Up @@ -339,4 +358,28 @@ mod tests {
let (_rem, result) = parse_version(buf).unwrap();
assert_eq!(result, "SIP/2.0");
}

#[test]
fn test_header_multi_value() {
let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\
From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\
To: <sip:voi18063@sip.cybercity.dk>\r\n\
Route: <sip:bob@biloxi.com>\r\n\
Route: <sip:carol@chicago.com>\r\n\
\r\n"
.as_bytes();

let (_, req) = sip_parse_request(buf).unwrap();
assert_eq!(req.method, "REGISTER");
assert_eq!(req.path, "sip:sip.cybercity.dk");
assert_eq!(req.version, "SIP/2.0");
assert_eq!(
req.headers["Route"].first().unwrap(),
"<sip:bob@biloxi.com>"
);
assert_eq!(
req.headers["Route"].get(1).unwrap(),
"<sip:carol@chicago.com>"
);
}
}

0 comments on commit cfb793c

Please sign in to comment.