diff options
Diffstat (limited to 'rust/src/http2/range.rs')
-rw-r--r-- | rust/src/http2/range.rs | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/rust/src/http2/range.rs b/rust/src/http2/range.rs new file mode 100644 index 0000000..9c96899 --- /dev/null +++ b/rust/src/http2/range.rs @@ -0,0 +1,259 @@ +/* Copyright (C) 2021 Open Information Security Foundation +* +* You can copy, redistribute or modify this Program under the terms of +* the GNU General Public License version 2 as published by the Free +* Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* version 2 along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +* 02110-1301, USA. +*/ + +use super::detect; +use crate::core::{ + Direction, Flow, HttpRangeContainerBlock, StreamingBufferConfig, SuricataFileContext, SC, +}; +use crate::http2::http2::HTTP2Transaction; +use crate::http2::http2::SURICATA_HTTP2_FILE_CONFIG; + +use nom7::branch::alt; +use nom7::bytes::streaming::{take_till, take_while}; +use nom7::character::complete::{char, digit1}; +use nom7::combinator::{map_res, value}; +use nom7::error::{make_error, ErrorKind}; +use nom7::{Err, IResult}; +use std::os::raw::c_uchar; +use std::str::FromStr; + +#[derive(Debug)] +#[repr(C)] +pub struct HTTPContentRange { + pub start: i64, + pub end: i64, + pub size: i64, +} + +pub fn http2_parse_content_range_star(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { + let (i2, _) = char('*')(input)?; + let (i2, _) = char('/')(i2)?; + let (i2, size) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(i2)?; + return Ok(( + i2, + HTTPContentRange { + start: -1, + end: -1, + size, + }, + )); +} + +pub fn http2_parse_content_range_def(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { + let (i2, start) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(input)?; + let (i2, _) = char('-')(i2)?; + let (i2, end) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(i2)?; + let (i2, _) = char('/')(i2)?; + let (i2, size) = alt(( + value(-1, char('*')), + map_res(map_res(digit1, std::str::from_utf8), i64::from_str), + ))(i2)?; + return Ok((i2, HTTPContentRange { start, end, size })); +} + +fn http2_parse_content_range(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { + let (i2, _) = take_while(|c| c == b' ')(input)?; + let (i2, _) = take_till(|c| c == b' ')(i2)?; + let (i2, _) = take_while(|c| c == b' ')(i2)?; + return alt(( + http2_parse_content_range_star, + http2_parse_content_range_def, + ))(i2); +} + +pub fn http2_parse_check_content_range(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { + let (rem, v) = http2_parse_content_range(input)?; + if v.start > v.end || (v.end > 0 && v.size > 0 && v.end > v.size - 1) { + return Err(Err::Error(make_error(rem, ErrorKind::Verify))); + } + return Ok((rem, v)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http_parse_content_range( + cr: &mut HTTPContentRange, buffer: *const u8, buffer_len: u32, +) -> std::os::raw::c_int { + let slice = build_slice!(buffer, buffer_len as usize); + match http2_parse_content_range(slice) { + Ok((_, c)) => { + *cr = c; + return 0; + } + _ => { + return -1; + } + } +} + +fn http2_range_key_get(tx: &mut HTTP2Transaction) -> Result<(Vec<u8>, usize), ()> { + let hostv = detect::http2_frames_get_header_value_vec(tx, Direction::ToServer, ":authority")?; + let mut hostv = &hostv[..]; + if let Some(p) = hostv.iter().position(|&x| x == b':') { + hostv = &hostv[..p]; + } + let uriv = detect::http2_frames_get_header_value_vec(tx, Direction::ToServer, ":path")?; + let mut uriv = &uriv[..]; + if let Some(p) = uriv.iter().position(|&x| x == b'?') { + uriv = &uriv[..p]; + } + if let Some(p) = uriv.iter().rposition(|&x| x == b'/') { + uriv = &uriv[p..]; + } + let mut r = Vec::with_capacity(hostv.len() + uriv.len()); + r.extend_from_slice(hostv); + r.extend_from_slice(uriv); + return Ok((r, hostv.len())); +} + +pub fn http2_range_open( + tx: &mut HTTP2Transaction, v: &HTTPContentRange, flow: *const Flow, + cfg: &'static SuricataFileContext, dir: Direction, data: &[u8], +) { + if v.end <= 0 || v.size <= 0 { + // skipped for incomplete range information + return; + } + if v.end == v.size - 1 && v.start == 0 { + // whole file in one range + return; + } + let flags = if dir == Direction::ToServer { tx.ft_ts.file_flags } else { tx.ft_tc.file_flags }; + if let Ok((key, index)) = http2_range_key_get(tx) { + let name = &key[index..]; + tx.file_range = unsafe { + HttpRangeContainerOpenFile( + key.as_ptr(), + key.len() as u32, + flow, + v, + cfg.files_sbcfg, + name.as_ptr(), + name.len() as u16, + flags, + data.as_ptr(), + data.len() as u32, + ) + }; + } +} + +pub fn http2_range_append(cfg: &'static SuricataFileContext, fr: *mut HttpRangeContainerBlock, data: &[u8]) { + unsafe { + HttpRangeAppendData(cfg.files_sbcfg, fr, data.as_ptr(), data.len() as u32); + } +} + +pub fn http2_range_close( + tx: &mut HTTP2Transaction, dir: Direction, data: &[u8], +) { + let added = if let Some(c) = unsafe { SC } { + if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } { + let (files, flags) = if dir == Direction::ToServer { + (&mut tx.ft_ts.file, tx.ft_ts.file_flags) + } else { + (&mut tx.ft_tc.file, tx.ft_tc.file_flags) + }; + let added = (c.HTPFileCloseHandleRange)( + sfcm.files_sbcfg, + files, + flags, + tx.file_range, + data.as_ptr(), + data.len() as u32, + ); + (c.HttpRangeFreeBlock)(tx.file_range); + added + } else { + false + } + } else { + false + }; + tx.file_range = std::ptr::null_mut(); + if added { + tx.tx_data.incr_files_opened(); + } +} + +// Defined in app-layer-htp-range.h +extern "C" { + pub fn HttpRangeContainerOpenFile( + key: *const c_uchar, keylen: u32, f: *const Flow, cr: &HTTPContentRange, + sbcfg: *const StreamingBufferConfig, name: *const c_uchar, name_len: u16, flags: u16, + data: *const c_uchar, data_len: u32, + ) -> *mut HttpRangeContainerBlock; + pub fn HttpRangeAppendData( + cfg: *const StreamingBufferConfig, c: *mut HttpRangeContainerBlock, data: *const c_uchar, data_len: u32, + ) -> std::os::raw::c_int; +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_http2_parse_content_range() { + let buf0: &[u8] = " bytes */100".as_bytes(); + let r0 = http2_parse_content_range(buf0); + match r0 { + Ok((rem, rg)) => { + // Check the first message. + assert_eq!(rg.start, -1); + assert_eq!(rg.end, -1); + assert_eq!(rg.size, 100); + // And we should have no bytes left. + assert_eq!(rem.len(), 0); + } + _ => { + panic!("Result should have been ok."); + } + } + + let buf1: &[u8] = " bytes 10-20/200".as_bytes(); + let r1 = http2_parse_content_range(buf1); + match r1 { + Ok((rem, rg)) => { + // Check the first message. + assert_eq!(rg.start, 10); + assert_eq!(rg.end, 20); + assert_eq!(rg.size, 200); + // And we should have no bytes left. + assert_eq!(rem.len(), 0); + } + _ => { + panic!("Result should have been ok."); + } + } + + let buf2: &[u8] = " bytes 30-68/*".as_bytes(); + let r2 = http2_parse_content_range(buf2); + match r2 { + Ok((rem, rg)) => { + // Check the first message. + assert_eq!(rg.start, 30); + assert_eq!(rg.end, 68); + assert_eq!(rg.size, -1); + // And we should have no bytes left. + assert_eq!(rem.len(), 0); + } + _ => { + panic!("Result should have been ok."); + } + } + } +} |