summaryrefslogtreecommitdiffstats
path: root/rust/src/http2/detect.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
commita0aa2307322cd47bbf416810ac0292925e03be87 (patch)
tree37076262a026c4b48c8a0e84f44ff9187556ca35 /rust/src/http2/detect.rs
parentInitial commit. (diff)
downloadsuricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz
suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rust/src/http2/detect.rs')
-rw-r--r--rust/src/http2/detect.rs1098
1 files changed, 1098 insertions, 0 deletions
diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs
new file mode 100644
index 0000000..52b4119
--- /dev/null
+++ b/rust/src/http2/detect.rs
@@ -0,0 +1,1098 @@
+/* Copyright (C) 2020 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::http2::{
+ HTTP2Event, HTTP2Frame, HTTP2FrameTypeData, HTTP2State, HTTP2Transaction, HTTP2TransactionState,
+};
+use super::parser;
+use crate::core::Direction;
+use crate::detect::uint::{detect_match_uint, DetectUintData};
+use std::ffi::CStr;
+use std::str::FromStr;
+
+fn http2_tx_has_frametype(
+ tx: &mut HTTP2Transaction, direction: Direction, value: u8,
+) -> std::os::raw::c_int {
+ if direction == Direction::ToServer {
+ for i in 0..tx.frames_ts.len() {
+ if tx.frames_ts[i].header.ftype == value {
+ return 1;
+ }
+ }
+ } else {
+ for i in 0..tx.frames_tc.len() {
+ if tx.frames_tc[i].header.ftype == value {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_has_frametype(
+ tx: *mut std::os::raw::c_void, direction: u8, value: u8,
+) -> std::os::raw::c_int {
+ let tx = cast_pointer!(tx, HTTP2Transaction);
+ return http2_tx_has_frametype(tx, direction.into(), value);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_parse_frametype(
+ str: *const std::os::raw::c_char,
+) -> std::os::raw::c_int {
+ let ft_name: &CStr = CStr::from_ptr(str); //unsafe
+ if let Ok(s) = ft_name.to_str() {
+ if let Ok(x) = parser::HTTP2FrameType::from_str(s) {
+ return x as i32;
+ }
+ }
+ return -1;
+}
+
+fn http2_tx_has_errorcode(
+ tx: &mut HTTP2Transaction, direction: Direction, code: u32,
+) -> std::os::raw::c_int {
+ if direction == Direction::ToServer {
+ for i in 0..tx.frames_ts.len() {
+ match tx.frames_ts[i].data {
+ HTTP2FrameTypeData::GOAWAY(goaway) => {
+ if goaway.errorcode == code {
+ return 1;
+ }
+ }
+ HTTP2FrameTypeData::RSTSTREAM(rst) => {
+ if rst.errorcode == code {
+ return 1;
+ }
+ }
+ _ => {}
+ }
+ }
+ } else {
+ for i in 0..tx.frames_tc.len() {
+ match tx.frames_tc[i].data {
+ HTTP2FrameTypeData::GOAWAY(goaway) => {
+ if goaway.errorcode == code {
+ return 1;
+ }
+ }
+ HTTP2FrameTypeData::RSTSTREAM(rst) => {
+ if rst.errorcode == code {
+ return 1;
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_has_errorcode(
+ tx: *mut std::os::raw::c_void, direction: u8, code: u32,
+) -> std::os::raw::c_int {
+ let tx = cast_pointer!(tx, HTTP2Transaction);
+ return http2_tx_has_errorcode(tx, direction.into(), code);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_parse_errorcode(
+ str: *const std::os::raw::c_char,
+) -> std::os::raw::c_int {
+ let ft_name: &CStr = CStr::from_ptr(str); //unsafe
+ if let Ok(s) = ft_name.to_str() {
+ if let Ok(x) = parser::HTTP2ErrorCode::from_str(s) {
+ return x as i32;
+ }
+ }
+ return -1;
+}
+
+fn http2_tx_get_next_priority(
+ tx: &mut HTTP2Transaction, direction: Direction, nb: u32,
+) -> std::os::raw::c_int {
+ let mut pos = 0_u32;
+ if direction == Direction::ToServer {
+ for i in 0..tx.frames_ts.len() {
+ match &tx.frames_ts[i].data {
+ HTTP2FrameTypeData::PRIORITY(prio) => {
+ if pos == nb {
+ return prio.weight as i32;
+ } else {
+ pos += 1;
+ }
+ }
+ HTTP2FrameTypeData::HEADERS(hd) => {
+ if let Some(prio) = hd.priority {
+ if pos == nb {
+ return prio.weight as i32;
+ } else {
+ pos += 1;
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+ } else {
+ for i in 0..tx.frames_tc.len() {
+ match &tx.frames_tc[i].data {
+ HTTP2FrameTypeData::PRIORITY(prio) => {
+ if pos == nb {
+ return prio.weight as i32;
+ } else {
+ pos += 1;
+ }
+ }
+ HTTP2FrameTypeData::HEADERS(hd) => {
+ if let Some(prio) = hd.priority {
+ if pos == nb {
+ return prio.weight as i32;
+ } else {
+ pos += 1;
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+ return -1;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_next_priority(
+ tx: *mut std::os::raw::c_void, direction: u8, nb: u32,
+) -> std::os::raw::c_int {
+ let tx = cast_pointer!(tx, HTTP2Transaction);
+ return http2_tx_get_next_priority(tx, direction.into(), nb);
+}
+
+fn http2_tx_get_next_window(
+ tx: &mut HTTP2Transaction, direction: Direction, nb: u32,
+) -> std::os::raw::c_int {
+ let mut pos = 0_u32;
+ if direction == Direction::ToServer {
+ for i in 0..tx.frames_ts.len() {
+ if let HTTP2FrameTypeData::WINDOWUPDATE(wu) = tx.frames_ts[i].data {
+ if pos == nb {
+ return wu.sizeinc as i32;
+ } else {
+ pos += 1;
+ }
+ }
+ }
+ } else {
+ for i in 0..tx.frames_tc.len() {
+ if let HTTP2FrameTypeData::WINDOWUPDATE(wu) = tx.frames_tc[i].data {
+ if pos == nb {
+ return wu.sizeinc as i32;
+ } else {
+ pos += 1;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_next_window(
+ tx: *mut std::os::raw::c_void, direction: u8, nb: u32,
+) -> std::os::raw::c_int {
+ let tx = cast_pointer!(tx, HTTP2Transaction);
+ return http2_tx_get_next_window(tx, direction.into(), nb);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_detect_settingsctx_parse(
+ str: *const std::os::raw::c_char,
+) -> *mut std::os::raw::c_void {
+ let ft_name: &CStr = CStr::from_ptr(str); //unsafe
+ if let Ok(s) = ft_name.to_str() {
+ if let Ok((_, ctx)) = parser::http2_parse_settingsctx(s) {
+ let boxed = Box::new(ctx);
+ return Box::into_raw(boxed) as *mut _;
+ }
+ }
+ return std::ptr::null_mut();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_detect_settingsctx_free(ctx: *mut std::os::raw::c_void) {
+ // Just unbox...
+ std::mem::drop(Box::from_raw(ctx as *mut parser::DetectHTTP2settingsSigCtx));
+}
+
+fn http2_detect_settings_match(
+ set: &[parser::HTTP2FrameSettings], ctx: &parser::DetectHTTP2settingsSigCtx,
+) -> std::os::raw::c_int {
+ for e in set {
+ if e.id == ctx.id {
+ match &ctx.value {
+ None => {
+ return 1;
+ }
+ Some(x) => {
+ if detect_match_uint(x, e.value) {
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+fn http2_detect_settingsctx_match(
+ ctx: &mut parser::DetectHTTP2settingsSigCtx, tx: &mut HTTP2Transaction, direction: Direction,
+) -> std::os::raw::c_int {
+ if direction == Direction::ToServer {
+ for i in 0..tx.frames_ts.len() {
+ if let HTTP2FrameTypeData::SETTINGS(set) = &tx.frames_ts[i].data {
+ if http2_detect_settings_match(set, ctx) != 0 {
+ return 1;
+ }
+ }
+ }
+ } else {
+ for i in 0..tx.frames_tc.len() {
+ if let HTTP2FrameTypeData::SETTINGS(set) = &tx.frames_tc[i].data {
+ if http2_detect_settings_match(set, ctx) != 0 {
+ return 1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_detect_settingsctx_match(
+ ctx: *const std::os::raw::c_void, tx: *mut std::os::raw::c_void, direction: u8,
+) -> std::os::raw::c_int {
+ let ctx = cast_pointer!(ctx, parser::DetectHTTP2settingsSigCtx);
+ let tx = cast_pointer!(tx, HTTP2Transaction);
+ return http2_detect_settingsctx_match(ctx, tx, direction.into());
+}
+
+fn http2_detect_sizeupdate_match(
+ blocks: &[parser::HTTP2FrameHeaderBlock], ctx: &DetectUintData<u64>,
+) -> std::os::raw::c_int {
+ for block in blocks.iter() {
+ if block.error == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate
+ && detect_match_uint(ctx, block.sizeupdate)
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+fn http2_header_blocks(frame: &HTTP2Frame) -> Option<&[parser::HTTP2FrameHeaderBlock]> {
+ match &frame.data {
+ HTTP2FrameTypeData::HEADERS(hd) => {
+ return Some(&hd.blocks);
+ }
+ HTTP2FrameTypeData::CONTINUATION(hd) => {
+ return Some(&hd.blocks);
+ }
+ HTTP2FrameTypeData::PUSHPROMISE(hd) => {
+ return Some(&hd.blocks);
+ }
+ _ => {}
+ }
+ return None;
+}
+
+fn http2_detect_sizeupdatectx_match(
+ ctx: &mut DetectUintData<u64>, tx: &mut HTTP2Transaction, direction: Direction,
+) -> std::os::raw::c_int {
+ if direction == Direction::ToServer {
+ for i in 0..tx.frames_ts.len() {
+ if let Some(blocks) = http2_header_blocks(&tx.frames_ts[i]) {
+ if http2_detect_sizeupdate_match(blocks, ctx) != 0 {
+ return 1;
+ }
+ }
+ }
+ } else {
+ for i in 0..tx.frames_tc.len() {
+ if let Some(blocks) = http2_header_blocks(&tx.frames_tc[i]) {
+ if http2_detect_sizeupdate_match(blocks, ctx) != 0 {
+ return 1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_detect_sizeupdatectx_match(
+ ctx: *const std::os::raw::c_void, tx: *mut std::os::raw::c_void, direction: u8,
+) -> std::os::raw::c_int {
+ let ctx = cast_pointer!(ctx, DetectUintData<u64>);
+ let tx = cast_pointer!(tx, HTTP2Transaction);
+ return http2_detect_sizeupdatectx_match(ctx, tx, direction.into());
+}
+
+//TODOask better syntax between rs_http2_tx_get_header_name in argument
+// and rs_http2_detect_sizeupdatectx_match explicitly casting
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_header_name(
+ tx: &mut HTTP2Transaction, direction: u8, nb: u32, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ let mut pos = 0_u32;
+ match direction.into() {
+ Direction::ToServer => {
+ for i in 0..tx.frames_ts.len() {
+ if let Some(blocks) = http2_header_blocks(&tx.frames_ts[i]) {
+ if nb < pos + blocks.len() as u32 {
+ let value = &blocks[(nb - pos) as usize].name;
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ } else {
+ pos += blocks.len() as u32;
+ }
+ }
+ }
+ }
+ Direction::ToClient => {
+ for i in 0..tx.frames_tc.len() {
+ if let Some(blocks) = http2_header_blocks(&tx.frames_tc[i]) {
+ if nb < pos + blocks.len() as u32 {
+ let value = &blocks[(nb - pos) as usize].name;
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ } else {
+ pos += blocks.len() as u32;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+fn http2_frames_get_header_firstvalue<'a>(
+ tx: &'a mut HTTP2Transaction, direction: Direction, name: &str,
+) -> Result<&'a [u8], ()> {
+ let frames = if direction == Direction::ToServer {
+ &tx.frames_ts
+ } else {
+ &tx.frames_tc
+ };
+ for frame in frames {
+ if let Some(blocks) = http2_header_blocks(frame) {
+ for block in blocks.iter() {
+ if block.name == name.as_bytes() {
+ return Ok(&block.value);
+ }
+ }
+ }
+ }
+ return Err(());
+}
+
+// same as http2_frames_get_header_value but returns a new Vec
+// instead of using the transaction to store the result slice
+pub fn http2_frames_get_header_value_vec(
+ tx: &HTTP2Transaction, direction: Direction, name: &str,
+) -> Result<Vec<u8>, ()> {
+ let mut found = 0;
+ let mut vec = Vec::new();
+ let frames = if direction == Direction::ToServer {
+ &tx.frames_ts
+ } else {
+ &tx.frames_tc
+ };
+ for frame in frames {
+ if let Some(blocks) = http2_header_blocks(frame) {
+ for block in blocks.iter() {
+ if block.name == name.as_bytes() {
+ if found == 0 {
+ vec.extend_from_slice(&block.value);
+ found = 1;
+ } else if found == 1 {
+ vec.extend_from_slice(&[b',', b' ']);
+ vec.extend_from_slice(&block.value);
+ found = 2;
+ } else {
+ vec.extend_from_slice(&[b',', b' ']);
+ vec.extend_from_slice(&block.value);
+ }
+ }
+ }
+ }
+ }
+ if found == 0 {
+ return Err(());
+ } else {
+ return Ok(vec);
+ }
+}
+
+fn http2_frames_get_header_value<'a>(
+ tx: &'a mut HTTP2Transaction, direction: Direction, name: &str,
+) -> Result<&'a [u8], ()> {
+ let mut found = 0;
+ let mut vec = Vec::new();
+ let mut single: Result<&[u8], ()> = Err(());
+ let frames = if direction == Direction::ToServer {
+ &tx.frames_ts
+ } else {
+ &tx.frames_tc
+ };
+ for frame in frames {
+ if let Some(blocks) = http2_header_blocks(frame) {
+ for block in blocks.iter() {
+ if block.name == name.as_bytes() {
+ if found == 0 {
+ single = Ok(&block.value);
+ found = 1;
+ } else if found == 1 {
+ if let Ok(s) = single {
+ vec.extend_from_slice(s);
+ }
+ vec.extend_from_slice(&[b',', b' ']);
+ vec.extend_from_slice(&block.value);
+ found = 2;
+ } else {
+ vec.extend_from_slice(&[b',', b' ']);
+ vec.extend_from_slice(&block.value);
+ }
+ }
+ }
+ }
+ }
+ if found == 0 {
+ return Err(());
+ } else if found == 1 {
+ return single;
+ } else {
+ tx.escaped.push(vec);
+ let idx = tx.escaped.len() - 1;
+ let value = &tx.escaped[idx];
+ return Ok(value);
+ }
+}
+
+fn http2_tx_get_req_line(tx: &mut HTTP2Transaction) {
+ if !tx.req_line.is_empty() {
+ return;
+ }
+ let empty = Vec::new();
+ let mut req_line = Vec::new();
+ let method =
+ if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToServer, ":method") {
+ value
+ } else {
+ &empty
+ };
+ req_line.extend(method);
+ req_line.push(b' ');
+
+ let uri =
+ if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToServer, ":path") {
+ value
+ } else {
+ &empty
+ };
+ req_line.extend(uri);
+ req_line.extend(b" HTTP/2\r\n");
+ tx.req_line.extend(req_line)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_request_line(
+ tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ http2_tx_get_req_line(tx);
+ *buffer = tx.req_line.as_ptr(); //unsafe
+ *buffer_len = tx.req_line.len() as u32;
+ return 1;
+}
+
+fn http2_tx_get_resp_line(tx: &mut HTTP2Transaction) {
+ if !tx.resp_line.is_empty() {
+ return;
+ }
+ let empty = Vec::new();
+ let mut resp_line : Vec<u8> = Vec::new();
+
+ let status =
+ if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToClient, ":status") {
+ value
+ } else {
+ &empty
+ };
+ resp_line.extend(b"HTTP/2 ");
+ resp_line.extend(status);
+ resp_line.extend(b"\r\n");
+ tx.resp_line.extend(resp_line)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_response_line(
+ tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ http2_tx_get_resp_line(tx);
+ *buffer = tx.resp_line.as_ptr(); //unsafe
+ *buffer_len = tx.resp_line.len() as u32;
+ return 1;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_uri(
+ tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToServer, ":path") {
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_method(
+ tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToServer, ":method") {
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_host(
+ tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ if let Ok(value) = http2_frames_get_header_value(tx, Direction::ToServer, ":authority") {
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ return 0;
+}
+
+fn http2_lower(value: &[u8]) -> Option<Vec<u8>> {
+ for i in 0..value.len() {
+ if value[i].is_ascii_uppercase() {
+ // we got at least one upper character, need to transform
+ let mut vec: Vec<u8> = Vec::with_capacity(value.len());
+ vec.extend_from_slice(value);
+ for e in &mut vec {
+ e.make_ascii_lowercase();
+ }
+ return Some(vec);
+ }
+ }
+ return None;
+}
+
+// returns a tuple with the value and its size
+fn http2_normalize_host(value: &[u8]) -> &[u8] {
+ match value.iter().position(|&x| x == b'@') {
+ Some(i) => {
+ let value = &value[i+1..];
+ match value.iter().position(|&x| x == b':') {
+ Some(i) => {
+ return &value[..i];
+ }
+ None => {
+ return value;
+ }
+ }
+ }
+ None => {
+ match value.iter().position(|&x| x == b':') {
+ Some(i) => {
+ return &value[..i];
+ }
+ None => {
+ return value;
+ }
+ }
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_host_norm(
+ tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ if let Ok(value) = http2_frames_get_header_value(tx, Direction::ToServer, ":authority") {
+ let r = http2_normalize_host(value);
+ // r is a tuple with the value and its size
+ // this is useful when we only take a substring (before the port)
+ match http2_lower(r) {
+ Some(normval) => {
+ // In case we needed some normalization,
+ // the transaction needs to take ownership of this normalized host
+ tx.escaped.push(normval);
+ let idx = tx.escaped.len() - 1;
+ let resvalue = &tx.escaped[idx];
+ *buffer = resvalue.as_ptr(); //unsafe
+ *buffer_len = resvalue.len() as u32;
+ return 1;
+ }
+ None => {
+ *buffer = r.as_ptr(); //unsafe
+ *buffer_len = r.len() as u32;
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_useragent(
+ tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ if let Ok(value) = http2_frames_get_header_value(tx, Direction::ToServer, "user-agent") {
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_status(
+ tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToClient, ":status") {
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_cookie(
+ tx: &mut HTTP2Transaction, direction: u8, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ if direction == Direction::ToServer.into() {
+ if let Ok(value) = http2_frames_get_header_value(tx, Direction::ToServer, "cookie") {
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ } else if let Ok(value) = http2_frames_get_header_value(tx, Direction::ToClient, "set-cookie") {
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_header_value(
+ tx: &mut HTTP2Transaction, direction: u8, strname: *const std::os::raw::c_char,
+ buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ let hname: &CStr = CStr::from_ptr(strname); //unsafe
+ if let Ok(s) = hname.to_str() {
+ if let Ok(value) = http2_frames_get_header_value(tx, direction.into(), &s.to_lowercase()) {
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+fn http2_escape_header(blocks: &[parser::HTTP2FrameHeaderBlock], i: u32) -> Vec<u8> {
+ //minimum size + 2 for escapes
+ let normalsize = blocks[i as usize].value.len() + 2 + blocks[i as usize].name.len();
+ let mut vec = Vec::with_capacity(normalsize);
+ vec.extend_from_slice(&blocks[i as usize].name);
+ vec.extend_from_slice(&[b':', b' ']);
+ vec.extend_from_slice(&blocks[i as usize].value);
+ return vec;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_header_names(
+ tx: &mut HTTP2Transaction, direction: u8, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ let mut vec = vec![b'\r', b'\n'];
+ let frames = if direction & Direction::ToServer as u8 != 0 {
+ &tx.frames_ts
+ } else {
+ &tx.frames_tc
+ };
+ for frame in frames {
+ if let Some(blocks) = http2_header_blocks(frame) {
+ for block in blocks.iter() {
+ // we do not escape linefeeds in headers names
+ vec.extend_from_slice(&block.name);
+ vec.extend_from_slice(&[b'\r', b'\n']);
+ }
+ }
+ }
+ if vec.len() > 2 {
+ vec.extend_from_slice(&[b'\r', b'\n']);
+ tx.escaped.push(vec);
+ let idx = tx.escaped.len() - 1;
+ let value = &tx.escaped[idx];
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ return 0;
+}
+
+fn http2_header_iscookie(direction: Direction, hname: &[u8]) -> bool {
+ if let Ok(s) = std::str::from_utf8(hname) {
+ if direction == Direction::ToServer {
+ if s.to_lowercase() == "cookie" {
+ return true;
+ }
+ } else if s.to_lowercase() == "set-cookie" {
+ return true;
+ }
+ }
+ return false;
+}
+
+fn http2_header_trimspaces(value: &[u8]) -> &[u8] {
+ let mut start = 0;
+ let mut end = value.len();
+ while start < value.len() {
+ if value[start] == b' ' || value[start] == b'\t' {
+ start += 1;
+ } else {
+ break;
+ }
+ }
+ while end > start {
+ if value[end - 1] == b' ' || value[end - 1] == b'\t' {
+ end -= 1;
+ } else {
+ break;
+ }
+ }
+ return &value[start..end];
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_headers(
+ tx: &mut HTTP2Transaction, direction: u8, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ let mut vec = Vec::new();
+ let frames = if direction & Direction::ToServer as u8 != 0 {
+ &tx.frames_ts
+ } else {
+ &tx.frames_tc
+ };
+ for frame in frames {
+ if let Some(blocks) = http2_header_blocks(frame) {
+ for block in blocks.iter() {
+ if !http2_header_iscookie(direction.into(), &block.name) {
+ // we do not escape linefeeds nor : in headers names
+ vec.extend_from_slice(&block.name);
+ vec.extend_from_slice(&[b':', b' ']);
+ vec.extend_from_slice(http2_header_trimspaces(&block.value));
+ vec.extend_from_slice(&[b'\r', b'\n']);
+ }
+ }
+ }
+ }
+ if !vec.is_empty() {
+ tx.escaped.push(vec);
+ let idx = tx.escaped.len() - 1;
+ let value = &tx.escaped[idx];
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_headers_raw(
+ tx: &mut HTTP2Transaction, direction: u8, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ let mut vec = Vec::new();
+ let frames = if direction & Direction::ToServer as u8 != 0 {
+ &tx.frames_ts
+ } else {
+ &tx.frames_tc
+ };
+ for frame in frames {
+ if let Some(blocks) = http2_header_blocks(frame) {
+ for block in blocks.iter() {
+ // we do not escape linefeeds nor : in headers names
+ vec.extend_from_slice(&block.name);
+ vec.extend_from_slice(&[b':', b' ']);
+ vec.extend_from_slice(&block.value);
+ vec.extend_from_slice(&[b'\r', b'\n']);
+ }
+ }
+ }
+ if !vec.is_empty() {
+ tx.escaped.push(vec);
+ let idx = tx.escaped.len() - 1;
+ let value = &tx.escaped[idx];
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_header(
+ tx: &mut HTTP2Transaction, direction: u8, nb: u32, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+ let mut pos = 0_u32;
+ match direction.into() {
+ Direction::ToServer => {
+ for i in 0..tx.frames_ts.len() {
+ if let Some(blocks) = http2_header_blocks(&tx.frames_ts[i]) {
+ if nb < pos + blocks.len() as u32 {
+ let ehdr = http2_escape_header(blocks, nb - pos);
+ tx.escaped.push(ehdr);
+ let idx = tx.escaped.len() - 1;
+ let value = &tx.escaped[idx];
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ } else {
+ pos += blocks.len() as u32;
+ }
+ }
+ }
+ }
+ Direction::ToClient => {
+ for i in 0..tx.frames_tc.len() {
+ if let Some(blocks) = http2_header_blocks(&tx.frames_tc[i]) {
+ if nb < pos + blocks.len() as u32 {
+ let ehdr = http2_escape_header(blocks, nb - pos);
+ tx.escaped.push(ehdr);
+ let idx = tx.escaped.len() - 1;
+ let value = &tx.escaped[idx];
+ *buffer = value.as_ptr(); //unsafe
+ *buffer_len = value.len() as u32;
+ return 1;
+ } else {
+ pos += blocks.len() as u32;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+fn http2_tx_set_header(state: &mut HTTP2State, name: &[u8], input: &[u8]) {
+ let head = parser::HTTP2FrameHeader {
+ length: 0,
+ ftype: parser::HTTP2FrameType::Headers as u8,
+ flags: 0,
+ reserved: 0,
+ stream_id: 1,
+ };
+ let mut blocks = Vec::new();
+ let b = parser::HTTP2FrameHeaderBlock {
+ name: name.to_vec(),
+ value: input.to_vec(),
+ error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
+ sizeupdate: 0,
+ };
+ blocks.push(b);
+ let hs = parser::HTTP2FrameHeaders {
+ padlength: None,
+ priority: None,
+ blocks,
+ };
+ let txdata = HTTP2FrameTypeData::HEADERS(hs);
+ let tx = state.find_or_create_tx(&head, &txdata, Direction::ToServer).unwrap();
+ tx.frames_ts.push(HTTP2Frame {
+ header: head,
+ data: txdata,
+ });
+ //we do not expect more data from client
+ tx.state = HTTP2TransactionState::HTTP2StateHalfClosedClient;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_set_method(
+ state: &mut HTTP2State, buffer: *const u8, buffer_len: u32,
+) {
+ let slice = build_slice!(buffer, buffer_len as usize);
+ http2_tx_set_header(state, ":method".as_bytes(), slice)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_set_uri(
+ state: &mut HTTP2State, buffer: *const u8, buffer_len: u32,
+) {
+ let slice = build_slice!(buffer, buffer_len as usize);
+ http2_tx_set_header(state, ":path".as_bytes(), slice)
+}
+
+fn http2_tx_set_settings(state: &mut HTTP2State, input: &[u8]) {
+ match base64::decode(input) {
+ Ok(dec) => {
+ if dec.len() % 6 != 0 {
+ state.set_event(HTTP2Event::InvalidHTTP1Settings);
+ }
+
+ let head = parser::HTTP2FrameHeader {
+ length: dec.len() as u32,
+ ftype: parser::HTTP2FrameType::Settings as u8,
+ flags: 0,
+ reserved: 0,
+ stream_id: 0,
+ };
+
+ match parser::http2_parse_frame_settings(&dec) {
+ Ok((_, set)) => {
+ let txdata = HTTP2FrameTypeData::SETTINGS(set);
+ let tx = state.find_or_create_tx(&head, &txdata, Direction::ToServer).unwrap();
+ tx.frames_ts.push(HTTP2Frame {
+ header: head,
+ data: txdata,
+ });
+ }
+ Err(_) => {
+ state.set_event(HTTP2Event::InvalidHTTP1Settings);
+ }
+ }
+ }
+ Err(_) => {
+ state.set_event(HTTP2Event::InvalidHTTP1Settings);
+ }
+ }
+}
+
+fn http2_caseinsensitive_cmp(s1: &[u8], s2: &str) -> bool {
+ if let Ok(s) = std::str::from_utf8(s1) {
+ return s.to_lowercase() == s2;
+ }
+ return false;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_add_header(
+ state: &mut HTTP2State, name: *const u8, name_len: u32, value: *const u8, value_len: u32,
+) {
+ let slice_name = build_slice!(name, name_len as usize);
+ let slice_value = build_slice!(value, value_len as usize);
+ if slice_name == "HTTP2-Settings".as_bytes() {
+ http2_tx_set_settings(state, slice_value)
+ } else if http2_caseinsensitive_cmp(slice_name, "host") {
+ http2_tx_set_header(state, ":authority".as_bytes(), slice_value)
+ } else {
+ http2_tx_set_header(state, slice_name, slice_value)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+
+ #[test]
+ fn test_http2_normalize_host() {
+ let buf0 = "aBC.com:1234".as_bytes();
+ let r0 = http2_normalize_host(buf0);
+ assert_eq!(r0, "aBC.com".as_bytes().to_vec());
+ let buf1 = "oisf.net".as_bytes();
+ let r1 = http2_normalize_host(buf1);
+ assert_eq!(r1, "oisf.net".as_bytes().to_vec());
+ let buf2 = "localhost:3000".as_bytes();
+ let r2 = http2_normalize_host(buf2);
+ assert_eq!(r2, "localhost".as_bytes().to_vec());
+ let buf3 = "user:pass@localhost".as_bytes();
+ let r3 = http2_normalize_host(buf3);
+ assert_eq!(r3, "localhost".as_bytes().to_vec());
+ let buf4 = "user:pass@localhost:123".as_bytes();
+ let r4 = http2_normalize_host(buf4);
+ assert_eq!(r4, "localhost".as_bytes().to_vec());
+ }
+
+ #[test]
+ fn test_http2_header_trimspaces() {
+ let buf0 = "nospaces".as_bytes();
+ let r0 = http2_header_trimspaces(buf0);
+ assert_eq!(r0, "nospaces".as_bytes());
+ let buf1 = " spaces\t".as_bytes();
+ let r1 = http2_header_trimspaces(buf1);
+ assert_eq!(r1, "spaces".as_bytes());
+ let buf2 = " \t".as_bytes();
+ let r2 = http2_header_trimspaces(buf2);
+ assert_eq!(r2, "".as_bytes());
+ }
+
+ #[test]
+ fn test_http2_frames_get_header_value() {
+ let mut tx = HTTP2Transaction::new();
+ let head = parser::HTTP2FrameHeader {
+ length: 0,
+ ftype: parser::HTTP2FrameType::Headers as u8,
+ flags: 0,
+ reserved: 0,
+ stream_id: 1,
+ };
+ let mut blocks = Vec::new();
+ let b = parser::HTTP2FrameHeaderBlock {
+ name: "Host".as_bytes().to_vec(),
+ value: "abc.com".as_bytes().to_vec(),
+ error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
+ sizeupdate: 0,
+ };
+ blocks.push(b);
+ let b2 = parser::HTTP2FrameHeaderBlock {
+ name: "Host".as_bytes().to_vec(),
+ value: "efg.net".as_bytes().to_vec(),
+ error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
+ sizeupdate: 0,
+ };
+ blocks.push(b2);
+ let hs = parser::HTTP2FrameHeaders {
+ padlength: None,
+ priority: None,
+ blocks,
+ };
+ let txdata = HTTP2FrameTypeData::HEADERS(hs);
+ tx.frames_ts.push(HTTP2Frame {
+ header: head,
+ data: txdata,
+ });
+ match http2_frames_get_header_value(&mut tx, Direction::ToServer, "Host") {
+ Ok(x) => {
+ assert_eq!(x, "abc.com, efg.net".as_bytes());
+ }
+ Err(e) => {
+ panic!("Result should not have been an error: {:?}", e);
+ }
+ }
+ }
+}