/src/suricata8/rust/src/sip/parser.rs
Line | Count | Source |
1 | | /* Copyright (C) 2019-2022 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | // written by Giuseppe Longo <giuseppe@glongo.it> |
19 | | |
20 | | use crate::sdp::parser::{sdp_parse_message, SdpMessage}; |
21 | | use nom7::bytes::streaming::{tag, take, take_while, take_while1}; |
22 | | use nom7::character::streaming::{char, crlf}; |
23 | | use nom7::character::{is_alphabetic, is_alphanumeric, is_digit, is_space}; |
24 | | use nom7::combinator::{map, map_res, opt}; |
25 | | use nom7::sequence::delimited; |
26 | | use nom7::{Err, IResult, Needed}; |
27 | | use std; |
28 | | use std::collections::HashMap; |
29 | | |
30 | | #[derive(Debug)] |
31 | | pub struct Header { |
32 | | pub name: String, |
33 | | pub value: String, |
34 | | } |
35 | | |
36 | | #[derive(Debug)] |
37 | | pub struct Request { |
38 | | pub method: String, |
39 | | pub path: String, |
40 | | pub version: String, |
41 | | pub headers: HashMap<String, Vec<String>>, |
42 | | |
43 | | pub request_line_len: u32, |
44 | | pub headers_len: u32, |
45 | | pub body_offset: u32, |
46 | | pub body_len: u32, |
47 | | pub body: Option<SdpMessage>, |
48 | | } |
49 | | |
50 | | #[derive(Debug)] |
51 | | pub struct Response { |
52 | | pub version: String, |
53 | | pub code: String, |
54 | | pub reason: String, |
55 | | pub headers: HashMap<String, Vec<String>>, |
56 | | pub response_line_len: u32, |
57 | | pub headers_len: u32, |
58 | | pub body_offset: u32, |
59 | | pub body_len: u32, |
60 | | pub body: Option<SdpMessage>, |
61 | | } |
62 | | |
63 | | /** |
64 | | * Valid tokens and chars are defined in RFC3261: |
65 | | * https://www.rfc-editor.org/rfc/rfc3261#section-25.1 |
66 | | */ |
67 | | #[inline] |
68 | 14.3M | fn is_token_char(b: u8) -> bool { |
69 | 14.3M | is_alphanumeric(b) || b"!%'*+-._`~".contains(&b) |
70 | 14.3M | } |
71 | | |
72 | | #[inline] |
73 | 191k | fn is_method_char(b: u8) -> bool { |
74 | 191k | is_alphabetic(b) |
75 | 191k | } |
76 | | |
77 | | #[inline] |
78 | 1.87M | fn is_request_uri_char(b: u8) -> bool { |
79 | 1.87M | is_alphanumeric(b) || is_token_char(b) || b"~#@:;=?+&$,/".contains(&b) |
80 | 1.87M | } |
81 | | |
82 | | #[inline] |
83 | 1.62M | fn is_version_char(b: u8) -> bool { |
84 | 1.62M | is_digit(b) || b".".contains(&b) |
85 | 1.62M | } |
86 | | |
87 | | #[inline] |
88 | 32.5M | fn is_reason_phrase(b: u8) -> bool { |
89 | 32.5M | is_alphanumeric(b) || is_token_char(b) || b"$&(),/:;=?@[\\]^ ".contains(&b) |
90 | 32.5M | } |
91 | | |
92 | 4.69M | fn is_header_name(b: u8) -> bool { |
93 | 4.69M | is_alphanumeric(b) || is_token_char(b) |
94 | 4.69M | } |
95 | | |
96 | 2.81M | fn is_header_value(b: u8) -> bool { |
97 | 2.81M | is_alphanumeric(b) || is_token_char(b) || b"\"#$&(),/;:<=>?@[]{}()^|~\\\t\n\r ".contains(&b) |
98 | 2.81M | } |
99 | | |
100 | 237k | fn expand_header_name(h: &str) -> &str { |
101 | 237k | match h { |
102 | 237k | "i" => "Call-ID", |
103 | 237k | "m" => "Contact", |
104 | 237k | "e" => "Content-Encoding", |
105 | 237k | "l" => "Content-Length", |
106 | 237k | "c" => "Content-Type", |
107 | 236k | "f" => "From", |
108 | 236k | "s" => "Subject", |
109 | 236k | "k" => "Supported", |
110 | 236k | "t" => "To", |
111 | 236k | "v" => "Via", |
112 | 236k | _ => h, |
113 | | } |
114 | 237k | } |
115 | | |
116 | 30.4k | pub fn parse_request(oi: &[u8]) -> IResult<&[u8], Request> { |
117 | 30.4k | let (i, method) = parse_method(oi)?; |
118 | 24.1k | let (i, _) = char(' ')(i)?; |
119 | 23.5k | let (i, path) = parse_request_uri(i)?; |
120 | 22.1k | let (i, _) = char(' ')(i)?; |
121 | 22.0k | let (i, version) = parse_version(i)?; |
122 | 21.0k | let (hi, _) = crlf(i)?; |
123 | 20.8k | let request_line_len = oi.len() - hi.len(); |
124 | 20.8k | let (phi, headers) = parse_headers(hi)?; |
125 | 20.4k | let headers_len = hi.len() - phi.len(); |
126 | 20.4k | let (bi, _) = crlf(phi)?; |
127 | 20.4k | let body_offset = oi.len() - bi.len(); |
128 | 20.4k | let (i, body) = opt(sdp_parse_message)(bi)?; |
129 | 20.4k | Ok(( |
130 | 20.4k | i, |
131 | 20.4k | Request { |
132 | 20.4k | method: method.into(), |
133 | 20.4k | path: path.into(), |
134 | 20.4k | version, |
135 | 20.4k | headers, |
136 | 20.4k | |
137 | 20.4k | request_line_len: request_line_len as u32, |
138 | 20.4k | headers_len: headers_len as u32, |
139 | 20.4k | body_offset: body_offset as u32, |
140 | 20.4k | body_len: bi.len() as u32, |
141 | 20.4k | body, |
142 | 20.4k | }, |
143 | 20.4k | )) |
144 | 30.4k | } |
145 | | |
146 | 386k | pub fn parse_response(oi: &[u8]) -> IResult<&[u8], Response> { |
147 | 386k | let (i, version) = parse_version(oi)?; |
148 | 383k | let (i, _) = char(' ')(i)?; |
149 | 383k | let (i, code) = parse_code(i)?; |
150 | 382k | let (i, _) = char(' ')(i)?; |
151 | 382k | let (i, reason) = parse_reason(i)?; |
152 | 379k | let (hi, _) = crlf(i)?; |
153 | 379k | let response_line_len = oi.len() - hi.len(); |
154 | 379k | let (phi, headers) = parse_headers(hi)?; |
155 | 368k | let headers_len = hi.len() - phi.len(); |
156 | 368k | let (bi, _) = crlf(phi)?; |
157 | 368k | let body_offset = oi.len() - bi.len(); |
158 | 368k | let (i, body) = opt(sdp_parse_message)(bi)?; |
159 | 368k | Ok(( |
160 | 368k | i, |
161 | 368k | Response { |
162 | 368k | version, |
163 | 368k | code: code.into(), |
164 | 368k | reason: reason.into(), |
165 | 368k | headers, |
166 | 368k | |
167 | 368k | response_line_len: response_line_len as u32, |
168 | 368k | headers_len: headers_len as u32, |
169 | 368k | body_offset: body_offset as u32, |
170 | 368k | body_len: bi.len() as u32, |
171 | 368k | body, |
172 | 368k | }, |
173 | 368k | )) |
174 | 386k | } |
175 | | |
176 | | #[inline] |
177 | 30.4k | fn parse_method(i: &[u8]) -> IResult<&[u8], &str> { |
178 | 30.4k | map_res(take_while(is_method_char), std::str::from_utf8)(i) |
179 | 30.4k | } |
180 | | |
181 | | #[inline] |
182 | 23.5k | fn parse_request_uri(i: &[u8]) -> IResult<&[u8], &str> { |
183 | 23.5k | map_res(take_while1(is_request_uri_char), std::str::from_utf8)(i) |
184 | 23.5k | } |
185 | | |
186 | | #[inline] |
187 | 408k | fn parse_version(i: &[u8]) -> IResult<&[u8], String> { |
188 | 408k | let (i, prefix) = map_res(tag("SIP/"), std::str::from_utf8)(i)?; |
189 | 405k | let (i, version) = map_res(take_while1(is_version_char), std::str::from_utf8)(i)?; |
190 | 404k | Ok((i, format!("{}{}", prefix, version))) |
191 | 408k | } |
192 | | |
193 | | #[inline] |
194 | 383k | fn parse_code(i: &[u8]) -> IResult<&[u8], &str> { |
195 | 383k | map_res(take(3_usize), std::str::from_utf8)(i) |
196 | 383k | } |
197 | | |
198 | | #[inline] |
199 | 382k | fn parse_reason(i: &[u8]) -> IResult<&[u8], &str> { |
200 | 382k | map_res(take_while(is_reason_phrase), std::str::from_utf8)(i) |
201 | 382k | } |
202 | | |
203 | | #[inline] |
204 | 238k | fn header_name(i: &[u8]) -> IResult<&[u8], &str> { |
205 | 238k | map_res(take_while(is_header_name), std::str::from_utf8)(i) |
206 | 238k | } |
207 | | |
208 | | #[inline] |
209 | 233k | fn header_value(i: &[u8]) -> IResult<&[u8], &str> { |
210 | 233k | map_res(parse_header_value, std::str::from_utf8)(i) |
211 | 233k | } |
212 | | |
213 | | #[inline] |
214 | 237k | fn hcolon(i: &[u8]) -> IResult<&[u8], char> { |
215 | 237k | delimited(take_while(is_space), char(':'), take_while(is_space))(i) |
216 | 237k | } |
217 | | |
218 | 238k | fn message_header(i: &[u8]) -> IResult<&[u8], Header> { |
219 | 238k | let (i, n) = map(header_name, expand_header_name)(i)?; |
220 | 237k | let (i, _) = hcolon(i)?; |
221 | 233k | let (i, v) = header_value(i)?; |
222 | 229k | let (i, _) = crlf(i)?; |
223 | 227k | Ok(( |
224 | 227k | i, |
225 | 227k | Header { |
226 | 227k | name: String::from(n), |
227 | 227k | value: String::from(v), |
228 | 227k | }, |
229 | 227k | )) |
230 | 238k | } |
231 | | |
232 | 389k | pub fn sip_take_line(i: &[u8]) -> IResult<&[u8], Option<String>> { |
233 | 389k | let (i, line) = map_res(take_while1(is_reason_phrase), std::str::from_utf8)(i)?; |
234 | 389k | Ok((i, Some(line.into()))) |
235 | 389k | } |
236 | | |
237 | 400k | pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, Vec<String>>> { |
238 | 400k | let mut headers_map: HashMap<String, Vec<String>> = HashMap::new(); |
239 | | loop { |
240 | 628k | match crlf(input) as IResult<&[u8], _> { |
241 | | Ok((_, _)) => { |
242 | 389k | break; |
243 | | } |
244 | 238k | Err(Err::Error(_)) => {} |
245 | 0 | Err(Err::Failure(_)) => {} |
246 | 1.02k | Err(Err::Incomplete(e)) => return Err(Err::Incomplete(e)), |
247 | | }; |
248 | 238k | let (rest, header) = message_header(input)?; |
249 | 227k | headers_map |
250 | 227k | .entry(header.name) |
251 | 227k | .or_default() |
252 | 227k | .push(header.value); |
253 | 227k | input = rest; |
254 | | } |
255 | | |
256 | 389k | Ok((input, headers_map)) |
257 | 400k | } |
258 | | |
259 | 233k | fn parse_header_value(buf: &[u8]) -> IResult<&[u8], &[u8]> { |
260 | 233k | let mut end_pos = 0; |
261 | 233k | let mut trail_spaces = 0; |
262 | 233k | let mut idx = 0; |
263 | 6.02M | while idx < buf.len() { |
264 | 6.02M | match buf[idx] { |
265 | | b'\n' => { |
266 | 229k | idx += 1; |
267 | 229k | if idx >= buf.len() { |
268 | 242 | return Err(Err::Incomplete(Needed::new(1))); |
269 | 229k | } |
270 | 229k | match buf[idx] { |
271 | | b' ' | b'\t' => { |
272 | 1.61k | idx += 1; |
273 | 1.61k | continue; |
274 | | } |
275 | | _ => { |
276 | 227k | return Ok((&buf[(end_pos + trail_spaces)..], &buf[..end_pos])); |
277 | | } |
278 | | } |
279 | | } |
280 | 2.73M | b' ' | b'\t' => { |
281 | 2.73M | trail_spaces += 1; |
282 | 2.73M | } |
283 | 246k | b'\r' => {} |
284 | 2.81M | b => { |
285 | 2.81M | trail_spaces = 0; |
286 | 2.81M | if !is_header_value(b) { |
287 | 4.31k | return Err(Err::Incomplete(Needed::new(1))); |
288 | 2.81M | } |
289 | 2.81M | end_pos = idx + 1; |
290 | | } |
291 | | } |
292 | 5.79M | idx += 1; |
293 | | } |
294 | 1.47k | Ok((&b""[..], buf)) |
295 | 233k | } |
296 | | |
297 | | #[cfg(test)] |
298 | | mod tests { |
299 | | |
300 | | use crate::sip::parser::*; |
301 | | |
302 | | #[test] |
303 | | fn test_parse_request() { |
304 | | let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\ |
305 | | From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\ |
306 | | To: <sip:voi18063@sip.cybercity.dk>\r\n\ |
307 | | Content-Length: 0\r\n\ |
308 | | \r\n" |
309 | | .as_bytes(); |
310 | | |
311 | | let (_, req) = parse_request(buf).unwrap(); |
312 | | assert_eq!(req.method, "REGISTER"); |
313 | | assert_eq!(req.path, "sip:sip.cybercity.dk"); |
314 | | assert_eq!(req.version, "SIP/2.0"); |
315 | | assert_eq!(req.headers["Content-Length"].first().unwrap(), "0"); |
316 | | } |
317 | | |
318 | | #[test] |
319 | | fn test_parse_request_trail_space_header() { |
320 | | let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\ |
321 | | From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\ |
322 | | To: <sip:voi18063@sip.cybercity.dk>\r\n\ |
323 | | Content-Length: 4 \r\n\ |
324 | | \r\nABCD" |
325 | | .as_bytes(); |
326 | | |
327 | | let (body, req) = parse_request(buf).expect("parsing failed"); |
328 | | assert_eq!(req.method, "REGISTER"); |
329 | | assert_eq!(req.path, "sip:sip.cybercity.dk"); |
330 | | assert_eq!(req.version, "SIP/2.0"); |
331 | | assert_eq!(req.headers["Content-Length"].first().unwrap(), "4"); |
332 | | assert_eq!(body, "ABCD".as_bytes()); |
333 | | } |
334 | | |
335 | | #[test] |
336 | | fn test_parse_response() { |
337 | | let buf: &[u8] = "SIP/2.0 401 Unauthorized\r\n\ |
338 | | \r\n" |
339 | | .as_bytes(); |
340 | | |
341 | | let (_, resp) = parse_response(buf).unwrap(); |
342 | | assert_eq!(resp.version, "SIP/2.0"); |
343 | | assert_eq!(resp.code, "401"); |
344 | | assert_eq!(resp.reason, "Unauthorized"); |
345 | | } |
346 | | |
347 | | #[test] |
348 | | fn test_parse_invalid_version() { |
349 | | let buf: &[u8] = "HTTP/1.1\r\n".as_bytes(); |
350 | | |
351 | | // This test must fail if 'HTTP/1.1' is accepted |
352 | | assert!(parse_version(buf).is_err()); |
353 | | } |
354 | | |
355 | | #[test] |
356 | | fn test_parse_valid_version() { |
357 | | let buf: &[u8] = "SIP/2.0\r\n".as_bytes(); |
358 | | |
359 | | let (_rem, result) = parse_version(buf).unwrap(); |
360 | | assert_eq!(result, "SIP/2.0"); |
361 | | } |
362 | | |
363 | | #[test] |
364 | | fn test_header_multi_value() { |
365 | | let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\ |
366 | | From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\ |
367 | | To: <sip:voi18063@sip.cybercity.dk>\r\n\ |
368 | | Route: <sip:bob@biloxi.com>\r\n\ |
369 | | Route: <sip:carol@chicago.com>\r\n\ |
370 | | \r\n" |
371 | | .as_bytes(); |
372 | | |
373 | | let (_, req) = parse_request(buf).unwrap(); |
374 | | assert_eq!(req.method, "REGISTER"); |
375 | | assert_eq!(req.path, "sip:sip.cybercity.dk"); |
376 | | assert_eq!(req.version, "SIP/2.0"); |
377 | | assert_eq!( |
378 | | req.headers["Route"].first().unwrap(), |
379 | | "<sip:bob@biloxi.com>" |
380 | | ); |
381 | | assert_eq!( |
382 | | req.headers["Route"].get(1).unwrap(), |
383 | | "<sip:carol@chicago.com>" |
384 | | ); |
385 | | } |
386 | | |
387 | | #[test] |
388 | | fn test_parse_request_large_body() { |
389 | | let body = vec![b'X'; 65536]; |
390 | | let mut buf: Vec<u8> = b"INVITE sip:bob@target.com SIP/2.0\r\n\ |
391 | | From: <sip:alice@attacker.com>;tag=abc123\r\n\ |
392 | | To: <sip:bob@target.com>\r\n\ |
393 | | Content-Type: application/sdp\r\n\ |
394 | | Content-Length: 65536\r\n\ |
395 | | \r\n" |
396 | | .to_vec(); |
397 | | let body_offset = buf.len(); |
398 | | buf.extend_from_slice(&body); |
399 | | |
400 | | let (rem, req) = parse_request(&buf).expect("parsing failed"); |
401 | | assert_eq!(req.method, "INVITE"); |
402 | | assert_eq!(req.body_offset as usize, body_offset); |
403 | | assert_eq!(req.body_len, 65536); |
404 | | assert_eq!(rem, &body[..]); |
405 | | } |
406 | | |
407 | | #[test] |
408 | | fn test_parse_response_large_body() { |
409 | | let body = vec![b'X'; 65536]; |
410 | | let mut buf: Vec<u8> = b"SIP/2.0 200 OK\r\n\ |
411 | | Content-Type: application/sdp\r\n\ |
412 | | Content-Length: 65536\r\n\ |
413 | | \r\n" |
414 | | .to_vec(); |
415 | | let body_offset = buf.len(); |
416 | | buf.extend_from_slice(&body); |
417 | | |
418 | | let (rem, resp) = parse_response(&buf).expect("parsing failed"); |
419 | | assert_eq!(resp.code, "200"); |
420 | | assert_eq!(resp.body_offset as usize, body_offset); |
421 | | assert_eq!(resp.body_len, 65536); |
422 | | assert_eq!(rem, &body[..]); |
423 | | } |
424 | | } |