summaryrefslogtreecommitdiffstats
path: root/rust/src/http2/range.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src/http2/range.rs')
-rw-r--r--rust/src/http2/range.rs259
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.");
+ }
+ }
+ }
+}