Coverage Report

Created: 2025-07-23 07:29

/src/suricata7/rust/src/mime/mod.rs
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (C) 2021 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
//! MIME protocol parser module.
19
20
use crate::common::nom7::take_until_and_consume;
21
use nom7::branch::alt;
22
use nom7::bytes::streaming::{tag, take_until, take_while};
23
use nom7::combinator::{complete, opt, rest};
24
use nom7::error::{make_error, ErrorKind};
25
use nom7::{Err, IResult};
26
use std;
27
use std::collections::HashMap;
28
29
#[derive(Clone)]
30
pub struct MIMEHeaderTokens<'a> {
31
    pub tokens: HashMap<&'a [u8], &'a [u8]>,
32
}
33
34
4.10M
pub fn mime_parse_value_delimited(input: &[u8]) -> IResult<&[u8], &[u8]> {
35
4.10M
    let (input, _) = tag("\"")(input)?;
36
608k
    let (input, value) = take_until("\"")(input)?;
37
605k
    let (input, _) = tag("\"")(input)?;
38
605k
    return Ok((input, value));
39
4.10M
}
40
41
4.31M
pub fn mime_parse_header_token(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
42
    // from RFC2047 : like ch.is_ascii_whitespace but without 0x0c FORM-FEED
43
4.32M
    let (input, _) = take_while(|ch: u8| ch == 0x20
44
4.31M
        || ch == 0x09
45
4.31M
        || ch == 0x0a
46
4.32M
        || ch == 0x0d)(input)?;
47
4.31M
    let (input, name) = take_until("=")(input)?;
48
4.10M
    let (input, _) = tag("=")(input)?;
49
4.10M
    let (input, value) = alt((
50
4.10M
        mime_parse_value_delimited,
51
4.10M
        complete(take_until(";")),
52
4.10M
        rest
53
4.10M
    ))(input)?;
54
4.09M
    let (input, _) = opt(complete(tag(";")))(input)?;
55
4.09M
    return Ok((input, (name, value)));
56
4.31M
}
57
58
817k
fn mime_parse_header_tokens(input: &[u8]) -> IResult<&[u8], MIMEHeaderTokens> {
59
817k
    let (mut input, _) = take_until_and_consume(b";")(input)?;
60
759k
    let mut tokens = HashMap::new();
61
4.85M
    while !input.is_empty() {
62
4.31M
        match mime_parse_header_token(input) {
63
4.09M
            Ok((rem, t)) => {
64
4.09M
                tokens.insert(t.0, t.1);
65
                // should never happen
66
4.09M
                debug_validate_bug_on!(input.len() == rem.len());
67
4.09M
                if input.len() == rem.len() {
68
                    //infinite loop
69
0
                    return Err(Err::Error(make_error(input, ErrorKind::Eof)));
70
4.09M
                }
71
4.09M
                input = rem;
72
            }
73
            Err(_) => {
74
                // keep first tokens is error in remaining buffer
75
215k
                break;
76
            }
77
        }
78
    }
79
759k
    return Ok((input, MIMEHeaderTokens { tokens }));
80
817k
}
81
82
817k
fn mime_find_header_token<'a>(
83
817k
    header: &'a [u8], token: &[u8], sections_values: &'a mut Vec<u8>,
84
817k
) -> Result<&'a [u8], ()> {
85
817k
    match mime_parse_header_tokens(header) {
86
759k
        Ok((_rem, t)) => {
87
759k
            // in case of multiple sections for the parameter cf RFC2231
88
759k
            let mut current_section_slice = Vec::new();
89
759k
90
759k
            // look for the specific token
91
759k
            match t.tokens.get(token) {
92
                // easy nominal case
93
182k
                Some(value) => return Ok(value),
94
                None => {
95
                    // check for initial section of a parameter
96
576k
                    current_section_slice.extend_from_slice(token);
97
576k
                    current_section_slice.extend_from_slice(b"*0");
98
576k
                    match t.tokens.get(&current_section_slice[..]) {
99
131k
                        Some(value) => {
100
131k
                            sections_values.extend_from_slice(value);
101
131k
                            let l = current_section_slice.len();
102
131k
                            current_section_slice[l - 1] = b'1';
103
131k
                        }
104
445k
                        None => return Err(()),
105
                    }
106
                }
107
            }
108
109
131k
            let mut current_section_seen = 1;
110
            // we have at least the initial section
111
            // try looping until we do not find anymore a next section
112
            loop {
113
427k
                match t.tokens.get(&current_section_slice[..]) {
114
296k
                    Some(value) => {
115
296k
                        sections_values.extend_from_slice(value);
116
296k
                        current_section_seen += 1;
117
296k
                        let nbdigits = current_section_slice.len() - token.len() - 1;
118
296k
                        current_section_slice.truncate(current_section_slice.len() - nbdigits);
119
296k
                        current_section_slice
120
296k
                            .extend_from_slice(current_section_seen.to_string().as_bytes());
121
296k
                    }
122
131k
                    None => return Ok(sections_values),
123
                }
124
            }
125
        }
126
        Err(_) => {
127
57.7k
            return Err(());
128
        }
129
    }
130
817k
}
131
132
// used on the C side
133
pub const RS_MIME_MAX_TOKEN_LEN: usize = 255;
134
135
#[no_mangle]
136
817k
pub unsafe extern "C" fn rs_mime_find_header_token(
137
817k
    hinput: *const u8, hlen: u32, tinput: *const u8, tlen: u32, outbuf: &mut [u8; 255],
138
817k
    outlen: *mut u32,
139
817k
) -> bool {
140
817k
    let hbuf = build_slice!(hinput, hlen as usize);
141
817k
    let tbuf = build_slice!(tinput, tlen as usize);
142
817k
    let mut sections_values = Vec::new();
143
817k
    if let Ok(value) = mime_find_header_token(hbuf, tbuf, &mut sections_values) {
144
        // limit the copy to the supplied buffer size
145
313k
        if value.len() <= RS_MIME_MAX_TOKEN_LEN {
146
309k
            outbuf[..value.len()].clone_from_slice(value);
147
309k
        } else {
148
3.60k
            outbuf.clone_from_slice(&value[..RS_MIME_MAX_TOKEN_LEN]);
149
3.60k
        }
150
313k
        *outlen = value.len() as u32;
151
313k
        return true;
152
503k
    }
153
503k
    return false;
154
817k
}
155
156
#[cfg(test)]
157
mod test {
158
    use super::*;
159
160
    #[test]
161
    fn test_mime_find_header_token() {
162
        let mut outvec = Vec::new();
163
        let undelimok = mime_find_header_token(
164
            "attachment; filename=test;".as_bytes(),
165
            "filename".as_bytes(),
166
            &mut outvec,
167
        );
168
        assert_eq!(undelimok, Ok("test".as_bytes()));
169
170
        let delimok = mime_find_header_token(
171
            "attachment; filename=\"test2\";".as_bytes(),
172
            "filename".as_bytes(),
173
            &mut outvec,
174
        );
175
        assert_eq!(delimok, Ok("test2".as_bytes()));
176
177
        let evasion_othertoken = mime_find_header_token(
178
            "attachment; dummy=\"filename=wrong\"; filename=real;".as_bytes(),
179
            "filename".as_bytes(),
180
            &mut outvec,
181
        );
182
        assert_eq!(evasion_othertoken, Ok("real".as_bytes()));
183
184
        let evasion_suffixtoken = mime_find_header_token(
185
            "attachment; notafilename=wrong; filename=good;".as_bytes(),
186
            "filename".as_bytes(),
187
            &mut outvec,
188
        );
189
        assert_eq!(evasion_suffixtoken, Ok("good".as_bytes()));
190
191
        let badending = mime_find_header_token(
192
            "attachment; filename=oksofar; badending".as_bytes(),
193
            "filename".as_bytes(),
194
            &mut outvec,
195
        );
196
        assert_eq!(badending, Ok("oksofar".as_bytes()));
197
198
        let missend = mime_find_header_token(
199
            "attachment; filename=test".as_bytes(),
200
            "filename".as_bytes(),
201
            &mut outvec,
202
        );
203
        assert_eq!(missend, Ok("test".as_bytes()));
204
205
        let spaces = mime_find_header_token(
206
            "attachment; filename=test me wrong".as_bytes(),
207
            "filename".as_bytes(),
208
            &mut outvec,
209
        );
210
        assert_eq!(spaces, Ok("test me wrong".as_bytes()));
211
212
        assert_eq!(outvec.len(), 0);
213
        let multi = mime_find_header_token(
214
            "attachment; filename*0=abc; filename*1=\"def\";".as_bytes(),
215
            "filename".as_bytes(),
216
            &mut outvec,
217
        );
218
        assert_eq!(multi, Ok("abcdef".as_bytes()));
219
        outvec.clear();
220
221
        let multi = mime_find_header_token(
222
            "attachment; filename*1=456; filename*0=\"123\"".as_bytes(),
223
            "filename".as_bytes(),
224
            &mut outvec,
225
        );
226
        assert_eq!(multi, Ok("123456".as_bytes()));
227
        outvec.clear();
228
    }
229
}