diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /rust/src/sip/parser.rs | |
parent | Initial commit. (diff) | |
download | suricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip |
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rust/src/sip/parser.rs')
-rw-r--r-- | rust/src/sip/parser.rs | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/rust/src/sip/parser.rs b/rust/src/sip/parser.rs new file mode 100644 index 0000000..a34bc26 --- /dev/null +++ b/rust/src/sip/parser.rs @@ -0,0 +1,325 @@ +/* Copyright (C) 2019-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Giuseppe Longo <giuseppe@glono.it> + +use nom7::bytes::streaming::{take, take_while, take_while1}; +use nom7::character::streaming::{char, crlf}; +use nom7::character::{is_alphabetic, is_alphanumeric, is_space}; +use nom7::combinator::map_res; +use nom7::sequence::delimited; +use nom7::{Err, IResult, Needed}; +use std; +use std::collections::HashMap; + +#[derive(Debug)] +pub struct Header { + pub name: String, + pub value: String, +} + +#[derive(Debug)] +pub struct Request { + pub method: String, + pub path: String, + pub version: String, + pub headers: HashMap<String, String>, + + pub request_line_len: u16, + pub headers_len: u16, + pub body_offset: u16, + pub body_len: u16, +} + +#[derive(Debug)] +pub struct Response { + pub version: String, + pub code: String, + pub reason: String, + + pub response_line_len: u16, + pub headers_len: u16, + pub body_offset: u16, + pub body_len: u16, +} + +#[inline] +fn is_token_char(b: u8) -> bool { + is_alphanumeric(b) || b"!%'*+-._`".contains(&b) +} + +#[inline] +fn is_method_char(b: u8) -> bool { + is_alphabetic(b) +} + +#[inline] +fn is_request_uri_char(b: u8) -> bool { + is_alphanumeric(b) || is_token_char(b) || b"~#@:".contains(&b) +} + +#[inline] +fn is_version_char(b: u8) -> bool { + is_alphanumeric(b) || b"./".contains(&b) +} + +#[inline] +fn is_reason_phrase(b: u8) -> bool { + is_alphanumeric(b) || is_token_char(b) || b"$&(),/:;=?@[\\]^ ".contains(&b) +} + +fn is_header_name(b: u8) -> bool { + is_alphanumeric(b) || is_token_char(b) +} + +fn is_header_value(b: u8) -> bool { + is_alphanumeric(b) || is_token_char(b) || b"\"#$&(),/;:<=>?@[]{}()^|~\\\t\n\r ".contains(&b) +} + +pub fn sip_parse_request(oi: &[u8]) -> IResult<&[u8], Request> { + let (i, method) = parse_method(oi)?; + let (i, _) = char(' ')(i)?; + let (i, path) = parse_request_uri(i)?; + let (i, _) = char(' ')(i)?; + let (i, version) = parse_version(i)?; + let (hi, _) = crlf(i)?; + let request_line_len = oi.len() - hi.len(); + let (phi, headers) = parse_headers(hi)?; + let headers_len = hi.len() - phi.len(); + let (bi, _) = crlf(phi)?; + let body_offset = oi.len() - bi.len(); + Ok(( + bi, + Request { + method: method.into(), + path: path.into(), + version: version.into(), + headers, + + request_line_len: request_line_len as u16, + headers_len: headers_len as u16, + body_offset: body_offset as u16, + body_len: bi.len() as u16, + }, + )) +} + +pub fn sip_parse_response(oi: &[u8]) -> IResult<&[u8], Response> { + let (i, version) = parse_version(oi)?; + let (i, _) = char(' ')(i)?; + let (i, code) = parse_code(i)?; + let (i, _) = char(' ')(i)?; + let (i, reason) = parse_reason(i)?; + let (hi, _) = crlf(i)?; + let response_line_len = oi.len() - hi.len(); + let (phi, _headers) = parse_headers(hi)?; + let headers_len = hi.len() - phi.len(); + let (bi, _) = crlf(phi)?; + let body_offset = oi.len() - bi.len(); + Ok(( + bi, + Response { + version: version.into(), + code: code.into(), + reason: reason.into(), + + response_line_len: response_line_len as u16, + headers_len: headers_len as u16, + body_offset: body_offset as u16, + body_len: bi.len() as u16, + }, + )) +} + +#[inline] +fn parse_method(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take_while(is_method_char), std::str::from_utf8)(i) +} + +#[inline] +fn parse_request_uri(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take_while1(is_request_uri_char), std::str::from_utf8)(i) +} + +#[inline] +fn parse_version(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take_while1(is_version_char), std::str::from_utf8)(i) +} + +#[inline] +fn parse_code(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take(3_usize), std::str::from_utf8)(i) +} + +#[inline] +fn parse_reason(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take_while(is_reason_phrase), std::str::from_utf8)(i) +} + +#[inline] +fn header_name(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take_while(is_header_name), std::str::from_utf8)(i) +} + +#[inline] +fn header_value(i: &[u8]) -> IResult<&[u8], &str> { + map_res(parse_header_value, std::str::from_utf8)(i) +} + +#[inline] +fn hcolon(i: &[u8]) -> IResult<&[u8], char> { + delimited(take_while(is_space), char(':'), take_while(is_space))(i) +} + +fn message_header(i: &[u8]) -> IResult<&[u8], Header> { + let (i, n) = header_name(i)?; + let (i, _) = hcolon(i)?; + let (i, v) = header_value(i)?; + let (i, _) = crlf(i)?; + Ok(( + i, + Header { + name: String::from(n), + value: String::from(v), + }, + )) +} + +pub fn sip_take_line(i: &[u8]) -> IResult<&[u8], Option<String>> { + let (i, line) = map_res(take_while1(is_reason_phrase), std::str::from_utf8)(i)?; + 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(); + loop { + match crlf(input) as IResult<&[u8], _> { + Ok((_, _)) => { + break; + } + Err(Err::Error(_)) => {} + Err(Err::Failure(_)) => {} + Err(Err::Incomplete(e)) => return Err(Err::Incomplete(e)), + }; + let (rest, header) = message_header(input)?; + headers_map.insert(header.name, header.value); + input = rest; + } + + Ok((input, headers_map)) +} + +fn parse_header_value(buf: &[u8]) -> IResult<&[u8], &[u8]> { + let mut end_pos = 0; + let mut trail_spaces = 0; + let mut idx = 0; + while idx < buf.len() { + match buf[idx] { + b'\n' => { + idx += 1; + if idx >= buf.len() { + return Err(Err::Incomplete(Needed::new(1))); + } + match buf[idx] { + b' ' | b'\t' => { + idx += 1; + continue; + } + _ => { + return Ok((&buf[(end_pos + trail_spaces)..], &buf[..end_pos])); + } + } + } + b' ' | b'\t' => { + trail_spaces += 1; + } + b'\r' => {} + b => { + trail_spaces = 0; + if !is_header_value(b) { + return Err(Err::Incomplete(Needed::new(1))); + } + end_pos = idx + 1; + } + } + idx += 1; + } + Ok((&b""[..], buf)) +} + +#[cfg(test)] +mod tests { + + use crate::sip::parser::*; + + #[test] + fn test_parse_request() { + 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\ + Content-Length: 0\r\n\ + \r\n" + .as_bytes(); + + match sip_parse_request(buf) { + Ok((_, req)) => { + 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!(false); + } + } + } + + #[test] + fn test_parse_request_trail_space_header() { + 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\ + Content-Length: 4 \r\n\ + \r\nABCD" + .as_bytes(); + + let (body, req) = sip_parse_request(buf).expect("parsing failed"); + 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!(body, "ABCD".as_bytes()); + } + + #[test] + fn test_parse_response() { + let buf: &[u8] = "SIP/2.0 401 Unauthorized\r\n\ + \r\n" + .as_bytes(); + + match sip_parse_response(buf) { + Ok((_, resp)) => { + assert_eq!(resp.version, "SIP/2.0"); + assert_eq!(resp.code, "401"); + assert_eq!(resp.reason, "Unauthorized"); + } + _ => { + assert!(false); + } + } + } +} |