/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(¤t_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(¤t_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 | | } |