diff options
Diffstat (limited to 'rust/src/http2/parser.rs')
-rw-r--r-- | rust/src/http2/parser.rs | 1050 |
1 files changed, 1050 insertions, 0 deletions
diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs new file mode 100644 index 0000000..adabeb2 --- /dev/null +++ b/rust/src/http2/parser.rs @@ -0,0 +1,1050 @@ +/* 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::huffman; +use crate::common::nom7::bits; +use crate::detect::uint::{detect_parse_uint, DetectUintData}; +use crate::http2::http2::{HTTP2DynTable, HTTP2_MAX_TABLESIZE}; +use nom7::bits::streaming::take as take_bits; +use nom7::branch::alt; +use nom7::bytes::streaming::{is_a, is_not, take, take_while}; +use nom7::combinator::{complete, cond, map_opt, opt, rest, verify}; +use nom7::error::{make_error, ErrorKind}; +use nom7::multi::many0; +use nom7::number::streaming::{be_u16, be_u24, be_u32, be_u8}; +use nom7::sequence::tuple; +use nom7::{Err, IResult}; +use std::fmt; +use std::str::FromStr; + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, Debug)] +pub enum HTTP2FrameType { + Data = 0, + Headers = 1, + Priority = 2, + RstStream = 3, + Settings = 4, + PushPromise = 5, + Ping = 6, + GoAway = 7, + WindowUpdate = 8, + Continuation = 9, +} + +impl fmt::Display for HTTP2FrameType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for HTTP2FrameType { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let su = s.to_uppercase(); + let su_slice: &str = &su; + match su_slice { + "DATA" => Ok(HTTP2FrameType::Data), + "HEADERS" => Ok(HTTP2FrameType::Headers), + "PRIORITY" => Ok(HTTP2FrameType::Priority), + "RSTSTREAM" => Ok(HTTP2FrameType::RstStream), + "SETTINGS" => Ok(HTTP2FrameType::Settings), + "PUSHPROMISE" => Ok(HTTP2FrameType::PushPromise), + "PING" => Ok(HTTP2FrameType::Ping), + "GOAWAY" => Ok(HTTP2FrameType::GoAway), + "WINDOWUPDATE" => Ok(HTTP2FrameType::WindowUpdate), + "CONTINUATION" => Ok(HTTP2FrameType::Continuation), + _ => Err(format!("'{}' is not a valid value for HTTP2FrameType", s)), + } + } +} + +#[derive(PartialEq, Eq, Debug)] +pub struct HTTP2FrameHeader { + //we could add detection on (GOAWAY) additional data + pub length: u32, + pub ftype: u8, + pub flags: u8, + pub reserved: u8, + pub stream_id: u32, +} + +pub fn http2_parse_frame_header(i: &[u8]) -> IResult<&[u8], HTTP2FrameHeader> { + let (i, length) = be_u24(i)?; + let (i, ftype) = be_u8(i)?; + let (i, flags) = be_u8(i)?; + let (i, b) = be_u32(i)?; + let (reserved, stream_id) = ((b >> 31) as u8, b & 0x7fff_ffff); + Ok(( + i, + HTTP2FrameHeader { + length, + ftype, + flags, + reserved, + stream_id, + }, + )) +} + +#[repr(u32)] +#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, Debug)] +pub enum HTTP2ErrorCode { + NoError = 0, + ProtocolError = 1, + InternalError = 2, + FlowControlError = 3, + SettingsTimeout = 4, + StreamClosed = 5, + FrameSizeError = 6, + RefusedStream = 7, + Cancel = 8, + CompressionError = 9, + ConnectError = 10, + EnhanceYourCalm = 11, + InadequateSecurity = 12, + Http11Required = 13, +} + +impl fmt::Display for HTTP2ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for HTTP2ErrorCode { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let su = s.to_uppercase(); + let su_slice: &str = &su; + match su_slice { + "NO_ERROR" => Ok(HTTP2ErrorCode::NoError), + "PROTOCOL_ERROR" => Ok(HTTP2ErrorCode::ProtocolError), + "FLOW_CONTROL_ERROR" => Ok(HTTP2ErrorCode::FlowControlError), + "SETTINGS_TIMEOUT" => Ok(HTTP2ErrorCode::SettingsTimeout), + "STREAM_CLOSED" => Ok(HTTP2ErrorCode::StreamClosed), + "FRAME_SIZE_ERROR" => Ok(HTTP2ErrorCode::FrameSizeError), + "REFUSED_STREAM" => Ok(HTTP2ErrorCode::RefusedStream), + "CANCEL" => Ok(HTTP2ErrorCode::Cancel), + "COMPRESSION_ERROR" => Ok(HTTP2ErrorCode::CompressionError), + "CONNECT_ERROR" => Ok(HTTP2ErrorCode::ConnectError), + "ENHANCE_YOUR_CALM" => Ok(HTTP2ErrorCode::EnhanceYourCalm), + "INADEQUATE_SECURITY" => Ok(HTTP2ErrorCode::InadequateSecurity), + "HTTP_1_1_REQUIRED" => Ok(HTTP2ErrorCode::Http11Required), + _ => Err(format!("'{}' is not a valid value for HTTP2ErrorCode", s)), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FrameGoAway { + pub errorcode: u32, //HTTP2ErrorCode +} + +pub fn http2_parse_frame_goaway(i: &[u8]) -> IResult<&[u8], HTTP2FrameGoAway> { + let (i, errorcode) = be_u32(i)?; + Ok((i, HTTP2FrameGoAway { errorcode })) +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FrameRstStream { + pub errorcode: u32, ////HTTP2ErrorCode +} + +pub fn http2_parse_frame_rststream(i: &[u8]) -> IResult<&[u8], HTTP2FrameRstStream> { + let (i, errorcode) = be_u32(i)?; + Ok((i, HTTP2FrameRstStream { errorcode })) +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FramePriority { + pub exclusive: u8, + pub dependency: u32, + pub weight: u8, +} + +pub fn http2_parse_frame_priority(i: &[u8]) -> IResult<&[u8], HTTP2FramePriority> { + let (i, b) = be_u32(i)?; + let (exclusive, dependency) = ((b >> 31) as u8, b & 0x7fff_ffff); + let (i, weight) = be_u8(i)?; + Ok(( + i, + HTTP2FramePriority { + exclusive, + dependency, + weight, + }, + )) +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FrameWindowUpdate { + pub reserved: u8, + pub sizeinc: u32, +} + +pub fn http2_parse_frame_windowupdate(i: &[u8]) -> IResult<&[u8], HTTP2FrameWindowUpdate> { + let (i, b) = be_u32(i)?; + let (reserved, sizeinc) = ((b >> 31) as u8, b & 0x7fff_ffff); + Ok((i, HTTP2FrameWindowUpdate { reserved, sizeinc })) +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FrameHeadersPriority { + pub exclusive: u8, + pub dependency: u32, + pub weight: u8, +} + +pub fn http2_parse_headers_priority(i: &[u8]) -> IResult<&[u8], HTTP2FrameHeadersPriority> { + let (i, b) = be_u32(i)?; + let (exclusive, dependency) = ((b >> 31) as u8, b & 0x7fff_ffff); + let (i, weight) = be_u8(i)?; + Ok(( + i, + HTTP2FrameHeadersPriority { + exclusive, + dependency, + weight, + }, + )) +} + +pub const HTTP2_STATIC_HEADERS_NUMBER: usize = 61; + +fn http2_frame_header_static(n: u64, dyn_headers: &HTTP2DynTable) -> Option<HTTP2FrameHeaderBlock> { + let (name, value) = match n { + 1 => (":authority", ""), + 2 => (":method", "GET"), + 3 => (":method", "POST"), + 4 => (":path", "/"), + 5 => (":path", "/index.html"), + 6 => (":scheme", "http"), + 7 => (":scheme", "https"), + 8 => (":status", "200"), + 9 => (":status", "204"), + 10 => (":status", "206"), + 11 => (":status", "304"), + 12 => (":status", "400"), + 13 => (":status", "404"), + 14 => (":status", "500"), + 15 => ("accept-charset", ""), + 16 => ("accept-encoding", "gzip, deflate"), + 17 => ("accept-language", ""), + 18 => ("accept-ranges", ""), + 19 => ("accept", ""), + 20 => ("access-control-allow-origin", ""), + 21 => ("age", ""), + 22 => ("allow", ""), + 23 => ("authorization", ""), + 24 => ("cache-control", ""), + 25 => ("content-disposition", ""), + 26 => ("content-encoding", ""), + 27 => ("content-language", ""), + 28 => ("content-length", ""), + 29 => ("content-location", ""), + 30 => ("content-range", ""), + 31 => ("content-type", ""), + 32 => ("cookie", ""), + 33 => ("date", ""), + 34 => ("etag", ""), + 35 => ("expect", ""), + 36 => ("expires", ""), + 37 => ("from", ""), + 38 => ("host", ""), + 39 => ("if-match", ""), + 40 => ("if-modified-since", ""), + 41 => ("if-none-match", ""), + 42 => ("if-range", ""), + 43 => ("if-unmodified-since", ""), + 44 => ("last-modified", ""), + 45 => ("link", ""), + 46 => ("location", ""), + 47 => ("max-forwards", ""), + 48 => ("proxy-authenticate", ""), + 49 => ("proxy-authorization", ""), + 50 => ("range", ""), + 51 => ("referer", ""), + 52 => ("refresh", ""), + 53 => ("retry-after", ""), + 54 => ("server", ""), + 55 => ("set-cookie", ""), + 56 => ("strict-transport-security", ""), + 57 => ("transfer-encoding", ""), + 58 => ("user-agent", ""), + 59 => ("vary", ""), + 60 => ("via", ""), + 61 => ("www-authenticate", ""), + _ => ("", ""), + }; + if !name.is_empty() { + return Some(HTTP2FrameHeaderBlock { + name: name.as_bytes().to_vec(), + value: value.as_bytes().to_vec(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + sizeupdate: 0, + }); + } else { + //use dynamic table + if n == 0 { + return Some(HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIndex0, + sizeupdate: 0, + }); + } else if dyn_headers.table.len() + HTTP2_STATIC_HEADERS_NUMBER < n as usize { + return Some(HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed, + sizeupdate: 0, + }); + } else { + let indyn = dyn_headers.table.len() - (n as usize - HTTP2_STATIC_HEADERS_NUMBER); + let headcopy = HTTP2FrameHeaderBlock { + name: dyn_headers.table[indyn].name.to_vec(), + value: dyn_headers.table[indyn].value.to_vec(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + sizeupdate: 0, + }; + return Some(headcopy); + } + } +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +pub enum HTTP2HeaderDecodeStatus { + HTTP2HeaderDecodeSuccess = 0, + HTTP2HeaderDecodeSizeUpdate = 1, + HTTP2HeaderDecodeError = 0x80, + HTTP2HeaderDecodeNotIndexed = 0x81, + HTTP2HeaderDecodeIntegerOverflow = 0x82, + HTTP2HeaderDecodeIndex0 = 0x83, +} + +impl fmt::Display for HTTP2HeaderDecodeStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Clone, Debug)] +pub struct HTTP2FrameHeaderBlock { + pub name: Vec<u8>, + pub value: Vec<u8>, + pub error: HTTP2HeaderDecodeStatus, + pub sizeupdate: u64, +} + +fn http2_parse_headers_block_indexed<'a>( + input: &'a [u8], dyn_headers: &HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(complete(tuple(( + verify(take_bits(1u8), |&x| x == 1), + take_bits(7u8), + ))))(input) + } + let (i2, indexed) = parser(input)?; + let (i3, indexreal) = http2_parse_var_uint(i2, indexed.1 as u64, 0x7F)?; + match http2_frame_header_static(indexreal, dyn_headers) { + Some(h) => Ok((i3, h)), + _ => Err(Err::Error(make_error(i3, ErrorKind::MapOpt))), + } +} + +fn http2_parse_headers_block_string(input: &[u8]) -> IResult<&[u8], Vec<u8>> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(tuple((take_bits(1u8), take_bits(7u8))))(input) + } + let (i1, huffslen) = parser(input)?; + let (i2, stringlen) = http2_parse_var_uint(i1, huffslen.1 as u64, 0x7F)?; + let (i3, data) = take(stringlen as usize)(i2)?; + if huffslen.0 == 0 { + return Ok((i3, data.to_vec())); + } else { + let (_, val) = bits(many0(huffman::http2_decode_huffman))(data)?; + return Ok((i3, val)); + } +} + +fn http2_parse_headers_block_literal_common<'a>( + input: &'a [u8], index: u64, dyn_headers: &HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + let (i3, name, error) = if index == 0 { + match http2_parse_headers_block_string(input) { + Ok((r, n)) => Ok((r, n, HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess)), + Err(e) => Err(e), + } + } else { + match http2_frame_header_static(index, dyn_headers) { + Some(x) => Ok(( + input, + x.name, + HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + )), + None => Ok(( + input, + Vec::new(), + HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed, + )), + } + }?; + let (i4, value) = http2_parse_headers_block_string(i3)?; + return Ok(( + i4, + HTTP2FrameHeaderBlock { + name, + value, + error, + sizeupdate: 0, + }, + )); +} + +fn http2_parse_headers_block_literal_incindex<'a>( + input: &'a [u8], dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(complete(tuple(( + verify(take_bits(2u8), |&x| x == 1), + take_bits(6u8), + ))))(input) + } + let (i2, indexed) = parser(input)?; + let (i3, indexreal) = http2_parse_var_uint(i2, indexed.1 as u64, 0x3F)?; + let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers); + match r { + Ok((r, head)) => { + let headcopy = HTTP2FrameHeaderBlock { + name: head.name.to_vec(), + value: head.value.to_vec(), + error: head.error, + sizeupdate: 0, + }; + if head.error == HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess { + dyn_headers.current_size += 32 + headcopy.name.len() + headcopy.value.len(); + //in case of overflow, best effort is to keep first headers + if dyn_headers.overflow > 0 { + if dyn_headers.overflow == 1 { + if dyn_headers.current_size <= (unsafe { HTTP2_MAX_TABLESIZE } as usize) { + //overflow had not yet happened + dyn_headers.table.push(headcopy); + } else if dyn_headers.current_size > dyn_headers.max_size { + //overflow happens, we cannot replace evicted headers + dyn_headers.overflow = 2; + } + } + } else { + dyn_headers.table.push(headcopy); + } + let mut toremove = 0; + while dyn_headers.current_size > dyn_headers.max_size + && toremove < dyn_headers.table.len() + { + dyn_headers.current_size -= + 32 + dyn_headers.table[toremove].name.len() + dyn_headers.table[toremove].value.len(); + toremove += 1; + } + dyn_headers.table.drain(0..toremove); + } + return Ok((r, head)); + } + Err(e) => { + return Err(e); + } + } +} + +fn http2_parse_headers_block_literal_noindex<'a>( + input: &'a [u8], dyn_headers: &HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(complete(tuple(( + verify(take_bits(4u8), |&x| x == 0), + take_bits(4u8), + ))))(input) + } + let (i2, indexed) = parser(input)?; + let (i3, indexreal) = http2_parse_var_uint(i2, indexed.1 as u64, 0xF)?; + let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers); + return r; +} + +fn http2_parse_headers_block_literal_neverindex<'a>( + input: &'a [u8], dyn_headers: &HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(complete(tuple(( + verify(take_bits(4u8), |&x| x == 1), + take_bits(4u8), + ))))(input) + } + let (i2, indexed) = parser(input)?; + let (i3, indexreal) = http2_parse_var_uint(i2, indexed.1 as u64, 0xF)?; + let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers); + return r; +} + +fn http2_parse_var_uint(input: &[u8], value: u64, max: u64) -> IResult<&[u8], u64> { + if value < max { + return Ok((input, value)); + } + let (i2, varia) = take_while(|ch| (ch & 0x80) != 0)(input)?; + let (i3, finalv) = be_u8(i2)?; + if varia.len() > 9 || (varia.len() == 9 && finalv > 1) { + // this will overflow u64 + return Ok((i3, 0)); + } + let mut varval = max; + for (i, e) in varia.iter().enumerate() { + varval += ((e & 0x7F) as u64) << (7 * i); + } + match varval.checked_add((finalv as u64) << (7 * varia.len())) { + None => { + return Err(Err::Error(make_error(i3, ErrorKind::LengthValue))); + } + Some(x) => { + return Ok((i3, x)); + } + } +} + +fn http2_parse_headers_block_dynamic_size<'a>( + input: &'a [u8], dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(complete(tuple(( + verify(take_bits(3u8), |&x| x == 1), + take_bits(5u8), + ))))(input) + } + let (i2, maxsize) = parser(input)?; + let (i3, maxsize2) = http2_parse_var_uint(i2, maxsize.1 as u64, 0x1F)?; + if (maxsize2 as usize) < dyn_headers.max_size { + //dyn_headers.max_size is updated later with all headers + //may evict entries + let mut toremove = 0; + while dyn_headers.current_size > (maxsize2 as usize) && toremove < dyn_headers.table.len() { + // we check dyn_headers.table as we may be in best effort + // because the previous maxsize was too big for us to retain all the headers + dyn_headers.current_size -= 32 + + dyn_headers.table[toremove].name.len() + + dyn_headers.table[toremove].value.len(); + toremove += 1; + } + dyn_headers.table.drain(0..toremove); + } + return Ok(( + i3, + HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate, + sizeupdate: maxsize2, + }, + )); +} + +fn http2_parse_headers_block<'a>( + input: &'a [u8], dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + //caller guarantees o have at least one byte + if input[0] & 0x80 != 0 { + return http2_parse_headers_block_indexed(input, dyn_headers); + } else if input[0] & 0x40 != 0 { + return http2_parse_headers_block_literal_incindex(input, dyn_headers); + } else if input[0] & 0x20 != 0 { + return http2_parse_headers_block_dynamic_size(input, dyn_headers); + } else if input[0] & 0x10 != 0 { + return http2_parse_headers_block_literal_neverindex(input, dyn_headers); + } else { + return http2_parse_headers_block_literal_noindex(input, dyn_headers); + } +} + +#[derive(Clone, Debug)] +pub struct HTTP2FrameHeaders { + pub padlength: Option<u8>, + pub priority: Option<HTTP2FrameHeadersPriority>, + pub blocks: Vec<HTTP2FrameHeaderBlock>, +} + +//end stream +pub const HTTP2_FLAG_HEADER_EOS: u8 = 0x1; +pub const HTTP2_FLAG_HEADER_END_HEADERS: u8 = 0x4; +pub const HTTP2_FLAG_HEADER_PADDED: u8 = 0x8; +const HTTP2_FLAG_HEADER_PRIORITY: u8 = 0x20; + +fn http2_parse_headers_blocks<'a>( + input: &'a [u8], dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], Vec<HTTP2FrameHeaderBlock>> { + let mut blocks = Vec::new(); + let mut i3 = input; + while !i3.is_empty() { + match http2_parse_headers_block(i3, dyn_headers) { + Ok((rem, b)) => { + blocks.push(b); + debug_validate_bug_on!(i3.len() == rem.len()); + if i3.len() == rem.len() { + //infinite loop + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + i3 = rem; + } + Err(Err::Error(ref err)) => { + // if we error from http2_parse_var_uint, we keep the first parsed headers + if err.code == ErrorKind::LengthValue { + blocks.push(HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIntegerOverflow, + sizeupdate: 0, + }); + break; + } + } + Err(x) => { + return Err(x); + } + } + } + return Ok((i3, blocks)); +} + +pub fn http2_parse_frame_headers<'a>( + input: &'a [u8], flags: u8, dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaders> { + let (i2, padlength) = cond(flags & HTTP2_FLAG_HEADER_PADDED != 0, be_u8)(input)?; + let (i3, priority) = cond( + flags & HTTP2_FLAG_HEADER_PRIORITY != 0, + http2_parse_headers_priority, + )(i2)?; + let (i3, blocks) = http2_parse_headers_blocks(i3, dyn_headers)?; + return Ok(( + i3, + HTTP2FrameHeaders { + padlength, + priority, + blocks, + }, + )); +} + +#[derive(Clone, Debug)] +pub struct HTTP2FramePushPromise { + pub padlength: Option<u8>, + pub reserved: u8, + pub stream_id: u32, + pub blocks: Vec<HTTP2FrameHeaderBlock>, +} + +pub fn http2_parse_frame_push_promise<'a>( + input: &'a [u8], flags: u8, dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FramePushPromise> { + let (i2, padlength) = cond(flags & HTTP2_FLAG_HEADER_PADDED != 0, be_u8)(input)?; + let (i3, stream_id) = bits(tuple((take_bits(1u8), take_bits(31u32))))(i2)?; + let (i3, blocks) = http2_parse_headers_blocks(i3, dyn_headers)?; + return Ok(( + i3, + HTTP2FramePushPromise { + padlength, + reserved: stream_id.0, + stream_id: stream_id.1, + blocks, + }, + )); +} + +#[derive(Clone, Debug)] +pub struct HTTP2FrameContinuation { + pub blocks: Vec<HTTP2FrameHeaderBlock>, +} + +pub fn http2_parse_frame_continuation<'a>( + input: &'a [u8], dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameContinuation> { + let (i3, blocks) = http2_parse_headers_blocks(input, dyn_headers)?; + return Ok((i3, HTTP2FrameContinuation { blocks })); +} + +#[repr(u16)] +#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, Debug)] +pub enum HTTP2SettingsId { + HeaderTableSize = 1, + EnablePush = 2, + MaxConcurrentStreams = 3, + InitialWindowSize = 4, + MaxFrameSize = 5, + MaxHeaderListSize = 6, +} + +impl fmt::Display for HTTP2SettingsId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for HTTP2SettingsId { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let su = s.to_uppercase(); + let su_slice: &str = &su; + match su_slice { + "SETTINGS_HEADER_TABLE_SIZE" => Ok(HTTP2SettingsId::HeaderTableSize), + "SETTINGS_ENABLE_PUSH" => Ok(HTTP2SettingsId::EnablePush), + "SETTINGS_MAX_CONCURRENT_STREAMS" => Ok(HTTP2SettingsId::MaxConcurrentStreams), + "SETTINGS_INITIAL_WINDOW_SIZE" => Ok(HTTP2SettingsId::InitialWindowSize), + "SETTINGS_MAX_FRAME_SIZE" => Ok(HTTP2SettingsId::MaxFrameSize), + "SETTINGS_MAX_HEADER_LIST_SIZE" => Ok(HTTP2SettingsId::MaxHeaderListSize), + _ => Err(format!("'{}' is not a valid value for HTTP2SettingsId", s)), + } + } +} + +pub struct DetectHTTP2settingsSigCtx { + pub id: HTTP2SettingsId, //identifier + pub value: Option<DetectUintData<u32>>, //optional value +} + +pub fn http2_parse_settingsctx(i: &str) -> IResult<&str, DetectHTTP2settingsSigCtx> { + let (i, _) = opt(is_a(" "))(i)?; + let (i, id) = map_opt(alt((complete(is_not(" <>=")), rest)), |s: &str| { + HTTP2SettingsId::from_str(s).ok() + })(i)?; + let (i, value) = opt(complete(detect_parse_uint))(i)?; + Ok((i, DetectHTTP2settingsSigCtx { id, value })) +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FrameSettings { + pub id: HTTP2SettingsId, + pub value: u32, +} + +fn http2_parse_frame_setting(i: &[u8]) -> IResult<&[u8], HTTP2FrameSettings> { + let (i, id) = map_opt(be_u16, num::FromPrimitive::from_u16)(i)?; + let (i, value) = be_u32(i)?; + Ok((i, HTTP2FrameSettings { id, value })) +} + +pub fn http2_parse_frame_settings(i: &[u8]) -> IResult<&[u8], Vec<HTTP2FrameSettings>> { + many0(complete(http2_parse_frame_setting))(i) +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::detect::uint::DetectUintMode; + + #[test] + fn test_http2_parse_header() { + let buf0: &[u8] = &[0x82]; + let mut dynh = HTTP2DynTable::new(); + let r0 = http2_parse_headers_block(buf0, &mut dynh); + match r0 { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, ":method".as_bytes().to_vec()); + assert_eq!(hd.value, "GET".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf1: &[u8] = &[0x53, 0x03, 0x2A, 0x2F, 0x2A]; + let r1 = http2_parse_headers_block(buf1, &mut dynh); + match r1 { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, "accept".as_bytes().to_vec()); + assert_eq!(hd.value, "*/*".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + assert_eq!(dynh.table.len(), 1); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf: &[u8] = &[ + 0x41, 0x8a, 0xa0, 0xe4, 0x1d, 0x13, 0x9d, 0x09, 0xb8, 0xc8, 0x00, 0x0f, + ]; + let result = http2_parse_headers_block(buf, &mut dynh); + match result { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, ":authority".as_bytes().to_vec()); + assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + assert_eq!(dynh.table.len(), 2); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf3: &[u8] = &[0xbe]; + let r3 = http2_parse_headers_block(buf3, &mut dynh); + match r3 { + Ok((remainder, hd)) => { + // same as before + assert_eq!(hd.name, ":authority".as_bytes().to_vec()); + assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + assert_eq!(dynh.table.len(), 2); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf4: &[u8] = &[0x80]; + let r4 = http2_parse_headers_block(buf4, &mut dynh); + match r4 { + Ok((remainder, hd)) => { + assert_eq!(hd.error, HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIndex0); + assert_eq!(remainder.len(), 0); + assert_eq!(dynh.table.len(), 2); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf2: &[u8] = &[ + 0x04, 0x94, 0x62, 0x43, 0x91, 0x8a, 0x47, 0x55, 0xa3, 0xa1, 0x89, 0xd3, 0x4d, 0x0c, + 0x1a, 0xa9, 0x0b, 0xe5, 0x79, 0xd3, 0x4d, 0x1f, + ]; + let r2 = http2_parse_headers_block(buf2, &mut dynh); + match r2 { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, ":path".as_bytes().to_vec()); + assert_eq!(hd.value, "/doc/manual/html/index.html".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + assert_eq!(dynh.table.len(), 2); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + /// Simple test of some valid data. + #[test] + fn test_http2_parse_settingsctx() { + let s = "SETTINGS_ENABLE_PUSH"; + let r = http2_parse_settingsctx(s); + match r { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::EnablePush); + match ctx.value { + Some(_) => { + panic!("Unexpected value"); + } + None => {} + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + //spaces in the end + let s1 = "SETTINGS_ENABLE_PUSH "; + let r1 = http2_parse_settingsctx(s1); + match r1 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::EnablePush); + if ctx.value.is_some() { + panic!("Unexpected value"); + } + assert_eq!(rem.len(), 1); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s2 = "SETTINGS_MAX_CONCURRENT_STREAMS 42"; + let r2 = http2_parse_settingsctx(s2); + match r2 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::MaxConcurrentStreams); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.arg1, 42); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s3 = "SETTINGS_MAX_CONCURRENT_STREAMS 42-68"; + let r3 = http2_parse_settingsctx(s3); + match r3 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::MaxConcurrentStreams); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.arg1, 42); + assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeRange); + assert_eq!(ctxval.arg2, 68); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s4 = "SETTINGS_MAX_CONCURRENT_STREAMS<54"; + let r4 = http2_parse_settingsctx(s4); + match r4 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::MaxConcurrentStreams); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.arg1, 54); + assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeLt); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s5 = "SETTINGS_MAX_CONCURRENT_STREAMS > 76"; + let r5 = http2_parse_settingsctx(s5); + match r5 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::MaxConcurrentStreams); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.arg1, 76); + assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeGt); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + } + + #[test] + fn test_http2_parse_headers_block_string() { + let buf: &[u8] = &[0x01, 0xFF]; + let r = http2_parse_headers_block_string(buf); + match r { + Ok((remainder, _)) => { + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + _ => { + panic!("Result should have been ok"); + } + } + let buf2: &[u8] = &[0x83, 0xFF, 0xFF, 0xEA]; + let r2 = http2_parse_headers_block_string(buf2); + match r2 { + Ok((remainder, _)) => { + assert_eq!(remainder.len(), 0); + } + _ => { + panic!("Result should have been ok"); + } + } + } + + #[test] + fn test_http2_parse_frame_header() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x06, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x64, + ]; + let result = http2_parse_frame_header(buf); + match result { + Ok((remainder, frame)) => { + // Check the first message. + assert_eq!(frame.length, 6); + assert_eq!(frame.ftype, HTTP2FrameType::Settings as u8); + assert_eq!(frame.flags, 0); + assert_eq!(frame.reserved, 0); + assert_eq!(frame.stream_id, 0); + + // And we should have 6 bytes left. + assert_eq!(remainder.len(), 6); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } +} |