You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/rust/src/http2/range.rs

256 lines
8.0 KiB
Rust

/* 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) = tx.files.get(dir);
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) = tx.files.get(dir);
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.");
}
}
}
}