/src/suricata7/rust/src/http2/range.rs
Line | Count | Source |
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 | | use super::detect; |
19 | | use crate::core::{ |
20 | | Direction, Flow, HttpRangeContainerBlock, StreamingBufferConfig, SuricataFileContext, SC, |
21 | | }; |
22 | | use crate::http2::http2::HTTP2Transaction; |
23 | | use crate::http2::http2::SURICATA_HTTP2_FILE_CONFIG; |
24 | | |
25 | | use nom7::branch::alt; |
26 | | use nom7::bytes::streaming::{take_till, take_while}; |
27 | | use nom7::character::complete::{char, digit1}; |
28 | | use nom7::combinator::{map_res, value}; |
29 | | use nom7::error::{make_error, ErrorKind}; |
30 | | use nom7::{Err, IResult}; |
31 | | use std::os::raw::c_uchar; |
32 | | use std::str::FromStr; |
33 | | |
34 | | #[derive(Debug)] |
35 | | #[repr(C)] |
36 | | pub struct HTTPContentRange { |
37 | | pub start: i64, |
38 | | pub end: i64, |
39 | | pub size: i64, |
40 | | } |
41 | | |
42 | 27.0k | pub fn http2_parse_content_range_star(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { |
43 | 27.0k | let (i2, _) = char('*')(input)?; |
44 | 1.23k | let (i2, _) = char('/')(i2)?; |
45 | 869 | let (i2, size) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(i2)?; |
46 | 564 | return Ok(( |
47 | 564 | i2, |
48 | 564 | HTTPContentRange { |
49 | 564 | start: -1, |
50 | 564 | end: -1, |
51 | 564 | size, |
52 | 564 | }, |
53 | 564 | )); |
54 | 27.0k | } |
55 | | |
56 | 26.5k | pub fn http2_parse_content_range_def(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { |
57 | 26.5k | let (i2, start) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(input)?; |
58 | 24.5k | let (i2, _) = char('-')(i2)?; |
59 | 23.5k | let (i2, end) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(i2)?; |
60 | 23.1k | let (i2, _) = char('/')(i2)?; |
61 | 22.6k | let (i2, size) = alt(( |
62 | 22.6k | value(-1, char('*')), |
63 | 22.6k | map_res(map_res(digit1, std::str::from_utf8), i64::from_str), |
64 | 22.6k | ))(i2)?; |
65 | 21.9k | return Ok((i2, HTTPContentRange { start, end, size })); |
66 | 26.5k | } |
67 | | |
68 | 29.7k | fn http2_parse_content_range(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { |
69 | 52.9k | let (i2, _) = take_while(|c| c == b' ')(input)?; |
70 | 1.61M | let (i2, _) = take_till(|c| c == b' ')(i2)?; |
71 | 72.7k | let (i2, _) = take_while(|c| c == b' ')(i2)?; |
72 | 27.0k | return alt(( |
73 | 27.0k | http2_parse_content_range_star, |
74 | 27.0k | http2_parse_content_range_def, |
75 | 27.0k | ))(i2); |
76 | 29.7k | } |
77 | | |
78 | 25.5k | pub fn http2_parse_check_content_range(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { |
79 | 25.5k | let (rem, v) = http2_parse_content_range(input)?; |
80 | 18.5k | if v.start > v.end || (v.end > 0 && v.size > 0 && v.end > v.size - 1) { |
81 | 367 | return Err(Err::Error(make_error(rem, ErrorKind::Verify))); |
82 | 18.1k | } |
83 | 18.1k | return Ok((rem, v)); |
84 | 25.5k | } |
85 | | |
86 | | #[no_mangle] |
87 | 4.12k | pub unsafe extern "C" fn rs_http_parse_content_range( |
88 | 4.12k | cr: &mut HTTPContentRange, buffer: *const u8, buffer_len: u32, |
89 | 4.12k | ) -> std::os::raw::c_int { |
90 | 4.12k | let slice = build_slice!(buffer, buffer_len as usize); |
91 | 4.12k | match http2_parse_content_range(slice) { |
92 | 3.97k | Ok((_, c)) => { |
93 | 3.97k | *cr = c; |
94 | 3.97k | return 0; |
95 | | } |
96 | | _ => { |
97 | 142 | return -1; |
98 | | } |
99 | | } |
100 | 4.12k | } |
101 | | |
102 | 17.1k | fn http2_range_key_get(tx: &mut HTTP2Transaction) -> Result<(Vec<u8>, usize), ()> { |
103 | 17.1k | let hostv = detect::http2_frames_get_header_value_vec(tx, Direction::ToServer, ":authority")?; |
104 | 14.4k | let mut hostv = &hostv[..]; |
105 | 3.54M | if let Some(p) = hostv.iter().position(|&x| x == b':') { |
106 | 1.71k | hostv = &hostv[..p]; |
107 | 12.7k | } |
108 | 14.4k | let uriv = detect::http2_frames_get_header_value_vec(tx, Direction::ToServer, ":path")?; |
109 | 14.1k | let mut uriv = &uriv[..]; |
110 | 28.1M | if let Some(p) = uriv.iter().position(|&x| x == b'?') { |
111 | 1.94k | uriv = &uriv[..p]; |
112 | 12.2k | } |
113 | 1.33M | if let Some(p) = uriv.iter().rposition(|&x| x == b'/') { |
114 | 9.69k | uriv = &uriv[p..]; |
115 | 9.69k | } |
116 | 14.1k | let mut r = Vec::with_capacity(hostv.len() + uriv.len()); |
117 | 14.1k | r.extend_from_slice(hostv); |
118 | 14.1k | r.extend_from_slice(uriv); |
119 | 14.1k | return Ok((r, hostv.len())); |
120 | 17.1k | } |
121 | | |
122 | 18.1k | pub fn http2_range_open( |
123 | 18.1k | tx: &mut HTTP2Transaction, v: &HTTPContentRange, flow: *const Flow, |
124 | 18.1k | cfg: &'static SuricataFileContext, dir: Direction, data: &[u8], |
125 | 18.1k | ) { |
126 | 18.1k | if v.end <= 0 || v.size <= 0 { |
127 | | // skipped for incomplete range information |
128 | 930 | return; |
129 | 17.2k | } |
130 | 17.2k | if v.end == v.size - 1 && v.start == 0 { |
131 | | // whole file in one range |
132 | 66 | return; |
133 | 17.1k | } |
134 | 17.1k | let flags = if dir == Direction::ToServer { tx.ft_ts.file_flags } else { tx.ft_tc.file_flags }; |
135 | 17.1k | if let Ok((key, index)) = http2_range_key_get(tx) { |
136 | 14.1k | let name = &key[index..]; |
137 | 14.1k | tx.file_range = unsafe { |
138 | 14.1k | HttpRangeContainerOpenFile( |
139 | 14.1k | key.as_ptr(), |
140 | 14.1k | key.len() as u32, |
141 | 14.1k | flow, |
142 | 14.1k | v, |
143 | 14.1k | cfg.files_sbcfg, |
144 | 14.1k | name.as_ptr(), |
145 | 14.1k | name.len() as u16, |
146 | 14.1k | flags, |
147 | 14.1k | data.as_ptr(), |
148 | 14.1k | data.len() as u32, |
149 | 14.1k | ) |
150 | 14.1k | }; |
151 | 14.1k | } |
152 | 18.1k | } |
153 | | |
154 | 5.14k | pub fn http2_range_append(cfg: &'static SuricataFileContext, fr: *mut HttpRangeContainerBlock, data: &[u8]) { |
155 | 5.14k | unsafe { |
156 | 5.14k | HttpRangeAppendData(cfg.files_sbcfg, fr, data.as_ptr(), data.len() as u32); |
157 | 5.14k | } |
158 | 5.14k | } |
159 | | |
160 | 2.54k | pub fn http2_range_close( |
161 | 2.54k | tx: &mut HTTP2Transaction, dir: Direction, data: &[u8], |
162 | 2.54k | ) { |
163 | 2.54k | let added = if let Some(c) = unsafe { SC } { |
164 | 2.54k | if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } { |
165 | 2.54k | let (files, flags) = if dir == Direction::ToServer { |
166 | 0 | (&mut tx.ft_ts.file, tx.ft_ts.file_flags) |
167 | | } else { |
168 | 2.54k | (&mut tx.ft_tc.file, tx.ft_tc.file_flags) |
169 | | }; |
170 | 2.54k | let added = (c.HTPFileCloseHandleRange)( |
171 | 2.54k | sfcm.files_sbcfg, |
172 | 2.54k | files, |
173 | 2.54k | flags, |
174 | 2.54k | tx.file_range, |
175 | 2.54k | data.as_ptr(), |
176 | 2.54k | data.len() as u32, |
177 | 2.54k | ); |
178 | 2.54k | (c.HttpRangeFreeBlock)(tx.file_range); |
179 | 2.54k | added |
180 | | } else { |
181 | 0 | false |
182 | | } |
183 | | } else { |
184 | 0 | false |
185 | | }; |
186 | 2.54k | tx.file_range = std::ptr::null_mut(); |
187 | 2.54k | if added { |
188 | 18 | tx.tx_data.incr_files_opened(); |
189 | 2.52k | } |
190 | 2.54k | } |
191 | | |
192 | | // Defined in app-layer-htp-range.h |
193 | | extern "C" { |
194 | | pub fn HttpRangeContainerOpenFile( |
195 | | key: *const c_uchar, keylen: u32, f: *const Flow, cr: &HTTPContentRange, |
196 | | sbcfg: *const StreamingBufferConfig, name: *const c_uchar, name_len: u16, flags: u16, |
197 | | data: *const c_uchar, data_len: u32, |
198 | | ) -> *mut HttpRangeContainerBlock; |
199 | | pub fn HttpRangeAppendData( |
200 | | cfg: *const StreamingBufferConfig, c: *mut HttpRangeContainerBlock, data: *const c_uchar, data_len: u32, |
201 | | ) -> std::os::raw::c_int; |
202 | | } |
203 | | |
204 | | #[cfg(test)] |
205 | | mod tests { |
206 | | |
207 | | use super::*; |
208 | | |
209 | | #[test] |
210 | | fn test_http2_parse_content_range() { |
211 | | let buf0: &[u8] = " bytes */100".as_bytes(); |
212 | | let r0 = http2_parse_content_range(buf0); |
213 | | match r0 { |
214 | | Ok((rem, rg)) => { |
215 | | // Check the first message. |
216 | | assert_eq!(rg.start, -1); |
217 | | assert_eq!(rg.end, -1); |
218 | | assert_eq!(rg.size, 100); |
219 | | // And we should have no bytes left. |
220 | | assert_eq!(rem.len(), 0); |
221 | | } |
222 | | _ => { |
223 | | panic!("Result should have been ok."); |
224 | | } |
225 | | } |
226 | | |
227 | | let buf1: &[u8] = " bytes 10-20/200".as_bytes(); |
228 | | let r1 = http2_parse_content_range(buf1); |
229 | | match r1 { |
230 | | Ok((rem, rg)) => { |
231 | | // Check the first message. |
232 | | assert_eq!(rg.start, 10); |
233 | | assert_eq!(rg.end, 20); |
234 | | assert_eq!(rg.size, 200); |
235 | | // And we should have no bytes left. |
236 | | assert_eq!(rem.len(), 0); |
237 | | } |
238 | | _ => { |
239 | | panic!("Result should have been ok."); |
240 | | } |
241 | | } |
242 | | |
243 | | let buf2: &[u8] = " bytes 30-68/*".as_bytes(); |
244 | | let r2 = http2_parse_content_range(buf2); |
245 | | match r2 { |
246 | | Ok((rem, rg)) => { |
247 | | // Check the first message. |
248 | | assert_eq!(rg.start, 30); |
249 | | assert_eq!(rg.end, 68); |
250 | | assert_eq!(rg.size, -1); |
251 | | // And we should have no bytes left. |
252 | | assert_eq!(rem.len(), 0); |
253 | | } |
254 | | _ => { |
255 | | panic!("Result should have been ok."); |
256 | | } |
257 | | } |
258 | | } |
259 | | } |