Coverage Report

Created: 2026-11-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/rust/src/sip/parser.rs
Line
Count
Source
1
/* Copyright (C) 2019-2026 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_until, 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::error::{Error, ErrorKind};
26
use nom7::sequence::delimited;
27
use nom7::{Err, IResult, Needed};
28
use std;
29
use std::collections::HashMap;
30
31
#[derive(Debug)]
32
pub struct Header {
33
    pub name: String,
34
    pub value: String,
35
}
36
37
#[derive(Debug)]
38
pub struct Request {
39
    pub method: String,
40
    pub path: String,
41
    pub version: String,
42
    pub headers: HashMap<String, Vec<String>>,
43
44
    pub request_line_len: u32,
45
    pub headers_len: u32,
46
    pub body_offset: u32,
47
    pub body_len: u32,
48
    pub body: Option<SdpMessage>,
49
}
50
51
#[derive(Debug)]
52
pub struct Response {
53
    pub version: String,
54
    pub code: String,
55
    pub reason: String,
56
    pub headers: HashMap<String, Vec<String>>,
57
    pub response_line_len: u32,
58
    pub headers_len: u32,
59
    pub body_offset: u32,
60
    pub body_len: u32,
61
    pub body: Option<SdpMessage>,
62
}
63
64
/**
65
 * Valid tokens and chars are defined in RFC3261:
66
 * https://www.rfc-editor.org/rfc/rfc3261#section-25.1
67
 */
68
#[inline]
69
1.62M
fn is_token_char(b: u8) -> bool {
70
1.62M
    is_alphanumeric(b) || b"!%'*+-._`~".contains(&b)
71
1.62M
}
72
73
#[inline]
74
879
fn is_method_char(b: u8) -> bool {
75
879
    is_alphabetic(b)
76
879
}
77
78
#[inline]
79
1.17M
fn is_request_uri_char(b: u8) -> bool {
80
1.17M
    is_alphanumeric(b) || is_token_char(b) || b"~#@:;=?+&$,/".contains(&b)
81
1.17M
}
82
83
#[inline]
84
1.05k
fn is_version_char(b: u8) -> bool {
85
1.05k
    is_digit(b) || b".".contains(&b)
86
1.05k
}
87
88
#[inline]
89
1.59M
fn is_reason_phrase(b: u8) -> bool {
90
1.59M
    is_alphanumeric(b) || is_token_char(b) || b"$&(),/:;=?@[\\]^ ".contains(&b)
91
1.59M
}
92
93
17.5k
fn is_header_name(b: u8) -> bool {
94
17.5k
    is_alphanumeric(b) || is_token_char(b)
95
17.5k
}
96
97
70.3k
fn is_header_value(b: u8) -> bool {
98
70.3k
    is_alphanumeric(b) || is_token_char(b) || b"\"#$&(),/;:<=>?@[]{}()^|~\\\t\n\r ".contains(&b)
99
70.3k
}
100
101
1.18k
fn expand_header_name(h: &str) -> &str {
102
1.18k
    match h {
103
1.18k
        "i" => "Call-ID",
104
1.18k
        "m" => "Contact",
105
1.18k
        "e" => "Content-Encoding",
106
1.18k
        "l" => "Content-Length",
107
1.18k
        "c" => "Content-Type",
108
1.18k
        "f" => "From",
109
1.18k
        "s" => "Subject",
110
1.18k
        "k" => "Supported",
111
1.18k
        "t" => "To",
112
1.17k
        "v" => "Via",
113
1.17k
        _ => h,
114
    }
115
1.18k
}
116
117
428
pub fn sip_probe_protocol(input: &[u8]) -> IResult<&[u8], ()> {
118
428
    let len = std::cmp::min(input.len(), 65536);
119
428
    let i = &input[..len];
120
121
428
    if tag::<_, _, Error<&[u8]>>("SIP/")(i).is_ok() {
122
0
        return Ok((input, ()));
123
428
    }
124
125
428
    if take_until::<_, _, Error<&[u8]>>("SIP/")(i).is_ok() {
126
47
        Ok((input, ()))
127
    } else {
128
381
        Err(Err::Error(Error::new(i, ErrorKind::Tag)))
129
    }
130
428
}
131
132
162
pub fn parse_request(oi: &[u8]) -> IResult<&[u8], Request> {
133
162
    let (i, method) = parse_method(oi)?;
134
159
    let (i, _) = char(' ')(i)?;
135
113
    let (i, path) = parse_request_uri(i)?;
136
90
    let (i, _) = char(' ')(i)?;
137
50
    let (i, version) = parse_version(i)?;
138
43
    let (hi, _) = crlf(i)?;
139
43
    let request_line_len = oi.len() - hi.len();
140
43
    let (phi, headers) = parse_headers(hi)?;
141
40
    let headers_len = hi.len() - phi.len();
142
40
    let (bi, _) = crlf(phi)?;
143
40
    let body_offset = oi.len() - bi.len();
144
40
    let (i, body) = opt(sdp_parse_message)(bi)?;
145
40
    Ok((
146
40
        i,
147
40
        Request {
148
40
            method: method.into(),
149
40
            path: path.into(),
150
40
            version,
151
40
            headers,
152
40
153
40
            request_line_len: request_line_len as u32,
154
40
            headers_len: headers_len as u32,
155
40
            body_offset: body_offset as u32,
156
40
            body_len: bi.len() as u32,
157
40
            body,
158
40
        },
159
40
    ))
160
162
}
161
162
252
pub fn parse_response(oi: &[u8]) -> IResult<&[u8], Response> {
163
252
    let (i, version) = parse_version(oi)?;
164
220
    let (i, _) = char(' ')(i)?;
165
219
    let (i, code) = parse_code(i)?;
166
218
    let (i, _) = char(' ')(i)?;
167
217
    let (i, reason) = parse_reason(i)?;
168
215
    let (hi, _) = crlf(i)?;
169
209
    let response_line_len = oi.len() - hi.len();
170
209
    let (phi, headers) = parse_headers(hi)?;
171
149
    let headers_len = hi.len() - phi.len();
172
149
    let (bi, _) = crlf(phi)?;
173
149
    let body_offset = oi.len() - bi.len();
174
149
    let (i, body) = opt(sdp_parse_message)(bi)?;
175
149
    Ok((
176
149
        i,
177
149
        Response {
178
149
            version,
179
149
            code: code.into(),
180
149
            reason: reason.into(),
181
149
            headers,
182
149
183
149
            response_line_len: response_line_len as u32,
184
149
            headers_len: headers_len as u32,
185
149
            body_offset: body_offset as u32,
186
149
            body_len: bi.len() as u32,
187
149
            body,
188
149
        },
189
149
    ))
190
252
}
191
192
#[inline]
193
162
fn parse_method(i: &[u8]) -> IResult<&[u8], &str> {
194
162
    map_res(take_while(is_method_char), std::str::from_utf8)(i)
195
162
}
196
197
#[inline]
198
113
fn parse_request_uri(i: &[u8]) -> IResult<&[u8], &str> {
199
113
    map_res(take_while1(is_request_uri_char), std::str::from_utf8)(i)
200
113
}
201
202
#[inline]
203
302
fn parse_version(i: &[u8]) -> IResult<&[u8], String> {
204
302
    let (i, prefix) = map_res(tag("SIP/"), std::str::from_utf8)(i)?;
205
265
    let (i, version) = map_res(take_while1(is_version_char), std::str::from_utf8)(i)?;
206
263
    Ok((i, format!("{}{}", prefix, version)))
207
302
}
208
209
#[inline]
210
219
fn parse_code(i: &[u8]) -> IResult<&[u8], &str> {
211
219
    map_res(take(3_usize), std::str::from_utf8)(i)
212
219
}
213
214
#[inline]
215
217
fn parse_reason(i: &[u8]) -> IResult<&[u8], &str> {
216
217
    map_res(take_while(is_reason_phrase), std::str::from_utf8)(i)
217
217
}
218
219
#[inline]
220
1.18k
fn header_name(i: &[u8]) -> IResult<&[u8], &str> {
221
1.18k
    map_res(take_while(is_header_name), std::str::from_utf8)(i)
222
1.18k
}
223
224
#[inline]
225
1.16k
fn header_value(i: &[u8]) -> IResult<&[u8], &str> {
226
1.16k
    map_res(parse_header_value, std::str::from_utf8)(i)
227
1.16k
}
228
229
#[inline]
230
1.18k
fn hcolon(i: &[u8]) -> IResult<&[u8], char> {
231
1.18k
    delimited(take_while(is_space), char(':'), take_while(is_space))(i)
232
1.18k
}
233
234
1.18k
fn message_header(i: &[u8]) -> IResult<&[u8], Header> {
235
1.18k
    let (i, n) = map(header_name, expand_header_name)(i)?;
236
1.18k
    let (i, _) = hcolon(i)?;
237
1.16k
    let (i, v) = header_value(i)?;
238
1.12k
    let (i, _) = crlf(i)?;
239
1.12k
    Ok((
240
1.12k
        i,
241
1.12k
        Header {
242
1.12k
            name: String::from(n),
243
1.12k
            value: String::from(v),
244
1.12k
        },
245
1.12k
    ))
246
1.18k
}
247
248
189
pub fn sip_take_line(i: &[u8]) -> IResult<&[u8], Option<String>> {
249
189
    let (i, line) = map_res(take_while1(is_reason_phrase), std::str::from_utf8)(i)?;
250
189
    Ok((i, Some(line.into())))
251
189
}
252
253
2.72k
pub fn sip_headers_complete(i: &[u8]) -> bool {
254
2.72k
    take_until::<_, _, Error<&[u8]>>("\r\n\r\n")(i).is_ok()
255
2.72k
}
256
257
252
pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, Vec<String>>> {
258
252
    let mut headers_map: HashMap<String, Vec<String>> = HashMap::new();
259
    loop {
260
1.37k
        match crlf(input) as IResult<&[u8], _> {
261
            Ok((_, _)) => {
262
189
                break;
263
            }
264
1.18k
            Err(Err::Error(_)) => {}
265
0
            Err(Err::Failure(_)) => {}
266
1
            Err(Err::Incomplete(e)) => return Err(Err::Incomplete(e)),
267
        };
268
1.18k
        let (rest, header) = message_header(input)?;
269
1.12k
        headers_map
270
1.12k
            .entry(header.name)
271
1.12k
            .or_default()
272
1.12k
            .push(header.value);
273
1.12k
        input = rest;
274
    }
275
276
189
    Ok((input, headers_map))
277
252
}
278
279
1.16k
fn parse_header_value(buf: &[u8]) -> IResult<&[u8], &[u8]> {
280
1.16k
    let mut end_pos = 0;
281
1.16k
    let mut trail_spaces = 0;
282
1.16k
    let mut idx = 0;
283
82.5k
    while idx < buf.len() {
284
82.5k
        match buf[idx] {
285
            b'\n' => {
286
1.90k
                idx += 1;
287
1.90k
                if idx >= buf.len() {
288
0
                    return Err(Err::Incomplete(Needed::new(1)));
289
1.90k
                }
290
1.90k
                match buf[idx] {
291
                    b' ' | b'\t' => {
292
783
                        idx += 1;
293
783
                        continue;
294
                    }
295
                    _ => {
296
1.12k
                        return Ok((&buf[(end_pos + trail_spaces)..], &buf[..end_pos]));
297
                    }
298
                }
299
            }
300
5.54k
            b' ' | b'\t' => {
301
5.54k
                trail_spaces += 1;
302
5.54k
            }
303
4.70k
            b'\r' => {}
304
70.3k
            b => {
305
70.3k
                trail_spaces = 0;
306
70.3k
                if !is_header_value(b) {
307
34
                    return Err(Err::Error(Error::new(buf, ErrorKind::IsNot)));
308
70.3k
                }
309
70.3k
                end_pos = idx + 1;
310
            }
311
        }
312
80.5k
        idx += 1;
313
    }
314
0
    Ok((&b""[..], buf))
315
1.16k
}
316
317
#[cfg(test)]
318
mod tests {
319
320
    use crate::sip::parser::*;
321
322
    #[test]
323
    fn test_parse_request() {
324
        let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\
325
                          From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\
326
                          To: <sip:voi18063@sip.cybercity.dk>\r\n\
327
                          Content-Length: 0\r\n\
328
                          \r\n"
329
            .as_bytes();
330
331
        let (_, req) = parse_request(buf).unwrap();
332
        assert_eq!(req.method, "REGISTER");
333
        assert_eq!(req.path, "sip:sip.cybercity.dk");
334
        assert_eq!(req.version, "SIP/2.0");
335
        assert_eq!(req.headers["Content-Length"].first().unwrap(), "0");
336
    }
337
338
    #[test]
339
    fn test_parse_request_trail_space_header() {
340
        let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\
341
                          From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\
342
                          To: <sip:voi18063@sip.cybercity.dk>\r\n\
343
                          Content-Length: 4  \r\n\
344
                          \r\nABCD"
345
            .as_bytes();
346
347
        let (body, req) = parse_request(buf).expect("parsing failed");
348
        assert_eq!(req.method, "REGISTER");
349
        assert_eq!(req.path, "sip:sip.cybercity.dk");
350
        assert_eq!(req.version, "SIP/2.0");
351
        assert_eq!(req.headers["Content-Length"].first().unwrap(), "4");
352
        assert_eq!(body, "ABCD".as_bytes());
353
    }
354
355
    #[test]
356
    fn test_parse_request_invalid_header_value() {
357
        let buf: &[u8] = b"REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\
358
                          From: <sip:voi18063@sip.cybercity.dk>\x00\r\n\
359
                          \r\n";
360
361
        assert!(matches!(parse_request(buf), Err(Err::Error(_))));
362
    }
363
364
    #[test]
365
    fn test_parse_response() {
366
        let buf: &[u8] = "SIP/2.0 401 Unauthorized\r\n\
367
                          \r\n"
368
            .as_bytes();
369
370
        let (_, resp) = parse_response(buf).unwrap();
371
        assert_eq!(resp.version, "SIP/2.0");
372
        assert_eq!(resp.code, "401");
373
        assert_eq!(resp.reason, "Unauthorized");
374
    }
375
376
    #[test]
377
    fn test_parse_invalid_version() {
378
        let buf: &[u8] = "HTTP/1.1\r\n".as_bytes();
379
380
        // This test must fail if 'HTTP/1.1' is accepted
381
        assert!(parse_version(buf).is_err());
382
    }
383
384
    #[test]
385
    fn test_parse_valid_version() {
386
        let buf: &[u8] = "SIP/2.0\r\n".as_bytes();
387
388
        let (_rem, result) = parse_version(buf).unwrap();
389
        assert_eq!(result, "SIP/2.0");
390
    }
391
392
    #[test]
393
    fn test_probe_sip_request() {
394
        let buf = b"REGISTER sip:sip.example.com SIP/2.0\r\n";
395
        assert!(sip_probe_protocol(buf).is_ok());
396
    }
397
398
    #[test]
399
    fn test_probe_sip_response() {
400
        let buf = b"SIP/2.0 200 OK\r\n";
401
        assert!(sip_probe_protocol(buf).is_ok());
402
    }
403
404
    #[test]
405
    fn test_header_multi_value() {
406
        let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\
407
                          From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\
408
                          To: <sip:voi18063@sip.cybercity.dk>\r\n\
409
                          Route: <sip:bob@biloxi.com>\r\n\
410
                          Route: <sip:carol@chicago.com>\r\n\
411
                          \r\n"
412
            .as_bytes();
413
414
        let (_, req) = parse_request(buf).unwrap();
415
        assert_eq!(req.method, "REGISTER");
416
        assert_eq!(req.path, "sip:sip.cybercity.dk");
417
        assert_eq!(req.version, "SIP/2.0");
418
        assert_eq!(
419
            req.headers["Route"].first().unwrap(),
420
            "<sip:bob@biloxi.com>"
421
        );
422
        assert_eq!(
423
            req.headers["Route"].get(1).unwrap(),
424
            "<sip:carol@chicago.com>"
425
        );
426
    }
427
428
    #[test]
429
    fn test_parse_request_large_body() {
430
        let body = vec![b'X'; 65536];
431
        let mut buf: Vec<u8> = b"INVITE sip:bob@target.com SIP/2.0\r\n\
432
                                 From: <sip:alice@attacker.com>;tag=abc123\r\n\
433
                                 To: <sip:bob@target.com>\r\n\
434
                                 Content-Type: application/sdp\r\n\
435
                                 Content-Length: 65536\r\n\
436
                                 \r\n"
437
            .to_vec();
438
        let body_offset = buf.len();
439
        buf.extend_from_slice(&body);
440
441
        let (rem, req) = parse_request(&buf).expect("parsing failed");
442
        assert_eq!(req.method, "INVITE");
443
        assert_eq!(req.body_offset as usize, body_offset);
444
        assert_eq!(req.body_len, 65536);
445
        assert_eq!(rem, &body[..]);
446
    }
447
448
    #[test]
449
    fn test_parse_response_large_body() {
450
        let body = vec![b'X'; 65536];
451
        let mut buf: Vec<u8> = b"SIP/2.0 200 OK\r\n\
452
                                 Content-Type: application/sdp\r\n\
453
                                 Content-Length: 65536\r\n\
454
                                 \r\n"
455
            .to_vec();
456
        let body_offset = buf.len();
457
        buf.extend_from_slice(&body);
458
459
        let (rem, resp) = parse_response(&buf).expect("parsing failed");
460
        assert_eq!(resp.code, "200");
461
        assert_eq!(resp.body_offset as usize, body_offset);
462
        assert_eq!(resp.body_len, 65536);
463
        assert_eq!(rem, &body[..]);
464
    }
465
}