Coverage Report

Created: 2026-09-06 07:25

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-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
}