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