mirror of https://github.com/OISF/suricata
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.
954 lines
37 KiB
Rust
954 lines
37 KiB
Rust
use crate::{
|
|
bstr::Bstr,
|
|
config::Config,
|
|
connection::{Connection, ConnectionFlags},
|
|
decompressors::HtpContentEncoding,
|
|
error::Result,
|
|
hook::DataHook,
|
|
log::Logger,
|
|
transaction::{HtpRequestProgress, HtpResponseProgress, HtpTransferCoding, Transaction},
|
|
transactions::Transactions,
|
|
util::{FlagOperations, HtpFlags},
|
|
HtpStatus,
|
|
};
|
|
use std::{any::Any, borrow::Cow, cell::Cell, net::IpAddr, time::SystemTime};
|
|
use time::OffsetDateTime;
|
|
|
|
/// Enumerates parsing state.
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
pub(crate) enum State {
|
|
/// Default state.
|
|
None,
|
|
/// State once a transaction is processed or about to be processed.
|
|
Idle,
|
|
/// State for request/response line parsing.
|
|
Line,
|
|
/// State for header parsing.
|
|
Headers,
|
|
/// State for finalizing chunked body data parsing.
|
|
BodyChunkedDataEnd,
|
|
/// State for chunked body data.
|
|
BodyChunkedData,
|
|
/// Parse the chunked length state.
|
|
BodyChunkedLength,
|
|
/// State to determine encoding of body data.
|
|
BodyDetermine,
|
|
/// State for finalizing transaction side.
|
|
Finalize,
|
|
// Used by request_state only
|
|
/// State for determining the request protocol.
|
|
Protocol,
|
|
/// State to determine if there is a CONNECT request.
|
|
ConnectCheck,
|
|
/// State to determine if inbound parsing needs to be suspended.
|
|
ConnectProbeData,
|
|
/// State to determine if inbound parsing can continue if it was suspended.
|
|
ConnectWaitResponse,
|
|
/// State to process request body data.
|
|
BodyIdentity,
|
|
/// State to consume remaining data in request buffer for the HTTP 0.9 case.
|
|
IgnoreDataAfterHTTP09,
|
|
// Used by response_state only
|
|
/// State to consume response remaining body data when content-length is unknown.
|
|
BodyIdentityStreamClose,
|
|
/// State to consume response body data when content-length is known.
|
|
BodyIdentityCLKnown,
|
|
}
|
|
|
|
/// Enumerates all stream states. Each connection has two streams, one
|
|
/// inbound and one outbound. Their states are tracked separately.
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
pub enum HtpStreamState {
|
|
/// Default stream state.
|
|
NEW,
|
|
/// State when connection is open.
|
|
OPEN,
|
|
/// State when connection is closed.
|
|
CLOSED,
|
|
/// State when stream produces a fatal error.
|
|
ERROR,
|
|
/// State for a tunnelled stream.
|
|
TUNNEL,
|
|
/// State when parsing is suspended and not consumed in order. This is to
|
|
/// allow processing on another stream.
|
|
DATA_OTHER,
|
|
/// State when we should stop parsing the associated connection.
|
|
STOP,
|
|
/// State when all current data in the stream has been processed.
|
|
DATA,
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone)]
|
|
/// This structure is used to pass data (for example
|
|
/// request and response body buffers or gaps) to parsers.
|
|
pub(crate) struct ParserData<'a> {
|
|
/// Ref to the data buffer.
|
|
data: Option<Cow<'a, [u8]>>,
|
|
// Length of data gap. Only set if is a gap.
|
|
gap_len: Option<usize>,
|
|
// Current position offset of the data to parse
|
|
position: Cell<usize>,
|
|
// Current callback data position
|
|
callback_position: usize,
|
|
}
|
|
|
|
impl ParserData<'_> {
|
|
/// Returns a pointer to the raw data associated with Data.
|
|
/// This returns a pointer to the entire data chunk.
|
|
pub(crate) fn data_ptr(&self) -> *const u8 {
|
|
self.data()
|
|
.as_ref()
|
|
.map(|data| data.as_ptr())
|
|
.unwrap_or(std::ptr::null())
|
|
}
|
|
|
|
/// Returns the unconsumed data
|
|
pub(crate) fn data(&self) -> Option<&[u8]> {
|
|
let data = self.data.as_ref()?;
|
|
if self.position.get() <= data.len() {
|
|
Some(&data[self.position.get()..])
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Returns the length of the unconsumed data.
|
|
pub(crate) fn len(&self) -> usize {
|
|
if let Some(gap_len) = self.gap_len {
|
|
if self.position.get() >= gap_len {
|
|
0
|
|
} else {
|
|
gap_len - self.position.get()
|
|
}
|
|
} else {
|
|
self.as_slice().len()
|
|
}
|
|
}
|
|
|
|
/// Returns how much data has been consumed so far
|
|
fn consumed_len(&self) -> usize {
|
|
self.position.get()
|
|
}
|
|
|
|
/// Return an immutable slice view of the unconsumed data.
|
|
pub(crate) fn as_slice(&self) -> &[u8] {
|
|
if let Some(data) = self.data.as_ref() {
|
|
if self.position.get() <= data.len() {
|
|
return &data[self.position.get()..];
|
|
}
|
|
}
|
|
b""
|
|
}
|
|
|
|
/// Determines if this chunk is a gap or not
|
|
pub(crate) fn is_gap(&self) -> bool {
|
|
self.gap_len.is_some()
|
|
}
|
|
|
|
/// Determine whether there is no more data to consume.
|
|
pub(crate) fn is_empty(&self) -> bool {
|
|
self.len() == 0
|
|
}
|
|
|
|
/// Set the position offset into the data for parsing
|
|
fn set_position(&self, position: usize) {
|
|
self.position.set(position);
|
|
}
|
|
|
|
/// Advances the internal position where we are parsing
|
|
pub(crate) fn consume(&self, consumed: usize) {
|
|
self.set_position(self.position.get() + consumed);
|
|
}
|
|
|
|
/// Decrements the internal position where we are parsing
|
|
fn unconsume(&self, unconsume: usize) {
|
|
if unconsume < self.position.get() {
|
|
self.set_position(self.position.get() - unconsume);
|
|
} else {
|
|
self.set_position(0);
|
|
}
|
|
}
|
|
|
|
/// Make an owned version of this data.
|
|
#[cfg(test)]
|
|
pub(crate) fn into_owned(self) -> ParserData<'static> {
|
|
ParserData {
|
|
data: self.data.map(|d| Cow::Owned(d.into_owned())),
|
|
gap_len: self.gap_len,
|
|
position: self.position,
|
|
callback_position: self.callback_position,
|
|
}
|
|
}
|
|
|
|
/// Callback data is raw data buffer content that is passed to the
|
|
/// application via the header and trailer data hooks.
|
|
///
|
|
/// This function will return any data that has been consumed but not
|
|
/// yet returned from this function.
|
|
pub(crate) fn callback_data(&mut self) -> &[u8] {
|
|
if let Some(data) = self.data.as_ref() {
|
|
if self.position.get() <= data.len() && self.callback_position <= self.position.get() {
|
|
let d = &data[self.callback_position..self.position.get()];
|
|
self.callback_position = self.position.get();
|
|
return d;
|
|
}
|
|
}
|
|
b""
|
|
}
|
|
|
|
/// Sets the callback start location to the current parsing location
|
|
pub(crate) fn reset_callback_start(&mut self) {
|
|
self.callback_position = self.position.get();
|
|
}
|
|
}
|
|
|
|
impl<'a> From<Option<&'a [u8]>> for ParserData<'a> {
|
|
fn from(data: Option<&'a [u8]>) -> Self {
|
|
ParserData {
|
|
data: data.map(Cow::Borrowed),
|
|
gap_len: None,
|
|
position: Cell::new(0),
|
|
callback_position: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a [u8]> for ParserData<'a> {
|
|
fn from(data: &'a [u8]) -> Self {
|
|
ParserData {
|
|
data: Some(Cow::Borrowed(data)),
|
|
gap_len: None,
|
|
position: Cell::new(0),
|
|
callback_position: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Vec<u8>> for ParserData<'static> {
|
|
fn from(data: Vec<u8>) -> Self {
|
|
ParserData {
|
|
data: Some(Cow::Owned(data)),
|
|
gap_len: None,
|
|
position: Cell::new(0),
|
|
callback_position: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a Vec<u8>> for ParserData<'a> {
|
|
fn from(data: &'a Vec<u8>) -> Self {
|
|
ParserData {
|
|
data: Some(Cow::Borrowed(data.as_slice())),
|
|
gap_len: None,
|
|
position: Cell::new(0),
|
|
callback_position: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<usize> for ParserData<'_> {
|
|
fn from(gap_len: usize) -> Self {
|
|
ParserData {
|
|
data: None,
|
|
gap_len: Some(gap_len),
|
|
position: Cell::new(0),
|
|
callback_position: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<(*const u8, usize)> for ParserData<'_> {
|
|
fn from((data, len): (*const u8, usize)) -> Self {
|
|
if data.is_null() {
|
|
if len > 0 {
|
|
ParserData::from(len)
|
|
} else {
|
|
ParserData::from(b"".as_ref())
|
|
}
|
|
} else {
|
|
unsafe { ParserData::from(std::slice::from_raw_parts(data, len)) }
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Stores information about the parsing process and associated transactions.
|
|
pub struct ConnectionParser {
|
|
// General fields
|
|
/// The logger structure associated with this parser
|
|
pub(crate) logger: Logger,
|
|
/// A reference to the current parser configuration structure.
|
|
pub(crate) cfg: &'static Config,
|
|
/// The connection structure associated with this parser.
|
|
pub(crate) conn: Connection,
|
|
/// Opaque user data associated with this parser.
|
|
pub(crate) user_data: Option<Box<dyn Any>>,
|
|
// Request parser fields
|
|
/// Parser inbound status. Starts as OK, but may turn into ERROR.
|
|
pub(crate) request_status: HtpStreamState,
|
|
/// Parser outbound status. Starts as OK, but may turn into ERROR.
|
|
pub(crate) response_status: HtpStreamState,
|
|
/// When true, this field indicates that there is unprocessed inbound data, and
|
|
/// that the response parsing code should stop at the end of the current request
|
|
/// in order to allow more requests to be produced.
|
|
pub(crate) response_data_other_at_tx_end: bool,
|
|
/// The time when the last request data chunk was received.
|
|
pub(crate) request_timestamp: OffsetDateTime,
|
|
/// How many bytes from the last input chunk have we consumed
|
|
/// This is mostly used from callbacks, where the caller
|
|
/// wants to know how far into the last chunk the parser is.
|
|
pub(crate) request_bytes_consumed: usize,
|
|
/// How many data chunks does the inbound connection stream consist of?
|
|
pub(crate) request_chunk_count: usize,
|
|
/// The index of the first chunk used in the current request.
|
|
pub(crate) request_chunk_request_index: usize,
|
|
/// Used to buffer a line of inbound data when buffering cannot be avoided.
|
|
pub(crate) request_buf: Bstr,
|
|
/// Stores the current value of a folded request header. Such headers span
|
|
/// multiple lines, and are processed only when all data is available.
|
|
pub(crate) request_header: Option<Bstr>,
|
|
/// The request body length declared in a valid request header. The key here
|
|
/// is "valid". This field will not be populated if the request contains both
|
|
/// a Transfer-Encoding header and a Content-Length header.
|
|
pub(crate) request_content_length: Option<u64>,
|
|
/// Holds the remaining request body length that we expect to read. This
|
|
/// field will be available only when the length of a request body is known
|
|
/// in advance, i.e. when request headers contain a Content-Length header.
|
|
pub(crate) request_body_data_left: Option<u64>,
|
|
/// Holds the amount of data that needs to be read from the
|
|
/// current data chunk. Only used with chunked request bodies.
|
|
pub(crate) request_chunked_length: Option<u64>,
|
|
/// Current request parser state.
|
|
pub(crate) request_state: State,
|
|
/// Previous request parser state. Used to detect state changes.
|
|
pub(crate) request_state_previous: State,
|
|
/// The hook that should be receiving raw connection data.
|
|
pub(crate) request_data_receiver_hook: Option<DataHook>,
|
|
|
|
// Response parser fields
|
|
/// The time when the last response data chunk was received.
|
|
pub(crate) response_timestamp: OffsetDateTime,
|
|
/// How many bytes from the last input chunk have we consumed
|
|
/// This is mostly used from callbacks, where the caller
|
|
/// wants to know how far into the last chunk the parser is.
|
|
pub(crate) response_bytes_consumed: usize,
|
|
/// Used to buffer a line of outbound data when buffering cannot be avoided.
|
|
pub(crate) response_buf: Bstr,
|
|
/// Stores the current value of a folded response header. Such headers span
|
|
/// multiple lines, and are processed only when all data is available.
|
|
pub(crate) response_header: Option<Bstr>,
|
|
/// The length of the current response body as presented in the
|
|
/// Content-Length response header.
|
|
pub(crate) response_content_length: Option<u64>,
|
|
/// The remaining length of the current response body, if known. Set to None otherwise.
|
|
pub(crate) response_body_data_left: Option<u64>,
|
|
/// Holds the amount of data that needs to be read from the
|
|
/// current response data chunk. Only used with chunked response bodies.
|
|
pub(crate) response_chunked_length: Option<u64>,
|
|
/// Current response parser state.
|
|
pub(crate) response_state: State,
|
|
/// Previous response parser state.
|
|
pub(crate) response_state_previous: State,
|
|
/// The hook that should be receiving raw connection data.
|
|
pub(crate) response_data_receiver_hook: Option<DataHook>,
|
|
|
|
/// Transactions processed by this parser
|
|
transactions: Transactions,
|
|
}
|
|
|
|
impl std::fmt::Debug for ConnectionParser {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
f.debug_struct("ConnectionParser")
|
|
.field("request_status", &self.request_status)
|
|
.field("response_status", &self.response_status)
|
|
.field("request_index", &self.request_index())
|
|
.field("response_index", &self.response_index())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl ConnectionParser {
|
|
/// Creates a new ConnectionParser with a preconfigured `Config` struct.
|
|
pub(crate) fn new(cfg: &'static Config) -> Self {
|
|
let conn = Connection::default();
|
|
let logger = Logger::new(conn.get_sender());
|
|
Self {
|
|
logger: logger.clone(),
|
|
cfg,
|
|
conn,
|
|
user_data: None,
|
|
request_status: HtpStreamState::NEW,
|
|
response_status: HtpStreamState::NEW,
|
|
response_data_other_at_tx_end: false,
|
|
request_timestamp: OffsetDateTime::from(SystemTime::now()),
|
|
request_bytes_consumed: 0,
|
|
request_chunk_count: 0,
|
|
request_chunk_request_index: 0,
|
|
request_buf: Bstr::new(),
|
|
request_header: None,
|
|
request_content_length: None,
|
|
request_body_data_left: None,
|
|
request_chunked_length: None,
|
|
request_state: State::Idle,
|
|
request_state_previous: State::None,
|
|
request_data_receiver_hook: None,
|
|
response_timestamp: OffsetDateTime::from(SystemTime::now()),
|
|
response_bytes_consumed: 0,
|
|
response_buf: Bstr::new(),
|
|
response_header: None,
|
|
response_content_length: None,
|
|
response_body_data_left: None,
|
|
response_chunked_length: None,
|
|
response_state: State::Idle,
|
|
response_state_previous: State::None,
|
|
response_data_receiver_hook: None,
|
|
transactions: Transactions::new(cfg, &logger),
|
|
}
|
|
}
|
|
|
|
/// Get the current request transaction
|
|
pub(crate) fn request(&mut self) -> Option<&Transaction> {
|
|
self.transactions.request()
|
|
}
|
|
|
|
/// Get the current request transaction
|
|
pub(crate) fn request_mut(&mut self) -> Option<&mut Transaction> {
|
|
self.transactions.request_mut()
|
|
}
|
|
|
|
/// Get the current response transaction
|
|
pub(crate) fn response(&mut self) -> Option<&Transaction> {
|
|
self.transactions.response()
|
|
}
|
|
|
|
/// Get the current response transaction
|
|
pub(crate) fn response_mut(&mut self) -> Option<&mut Transaction> {
|
|
self.transactions.response_mut()
|
|
}
|
|
|
|
/// Advance to the next request
|
|
/// Returns the next request transaction id
|
|
pub(crate) fn request_next(&mut self) -> usize {
|
|
// Detect pipelining.
|
|
if self.transactions.request_index() > self.transactions.response_index() {
|
|
self.conn.flags.set(ConnectionFlags::PIPELINED)
|
|
}
|
|
self.transactions.request_next()
|
|
}
|
|
|
|
/// Advance to the next response
|
|
/// Returns the next response transaction id
|
|
pub(crate) fn response_next(&mut self) -> usize {
|
|
self.transactions.response_next()
|
|
}
|
|
|
|
/// Get the index of the request transaction
|
|
pub(crate) fn request_index(&self) -> usize {
|
|
self.transactions.request_index()
|
|
}
|
|
|
|
/// Get the index of the response transaction
|
|
pub(crate) fn response_index(&self) -> usize {
|
|
self.transactions.response_index()
|
|
}
|
|
|
|
/// Get the number of transactions processed up to now
|
|
pub(crate) fn tx_size(&self) -> usize {
|
|
self.transactions.size()
|
|
}
|
|
|
|
/// Get a specific transaction
|
|
pub(crate) fn tx(&self, index: usize) -> Option<&Transaction> {
|
|
self.transactions.get(index)
|
|
}
|
|
|
|
/// Get a specific transaction by its index
|
|
pub(crate) fn tx_index(&mut self, index: usize) -> Option<&mut Transaction> {
|
|
self.transactions.get_index(index)
|
|
}
|
|
|
|
/// Get a specific transaction
|
|
pub(crate) fn tx_mut(&mut self, index: usize) -> Option<&mut Transaction> {
|
|
self.transactions.get_mut(index)
|
|
}
|
|
|
|
/// Handle the current state to be processed.
|
|
pub(crate) fn handle_request_state(&mut self, data: &mut ParserData) -> Result<()> {
|
|
match self.request_state {
|
|
State::None => Err(HtpStatus::ERROR),
|
|
State::Idle => self.request_idle(data),
|
|
State::IgnoreDataAfterHTTP09 => self.request_ignore_data_after_http_0_9(data),
|
|
State::Line => self.request_line(data),
|
|
State::Protocol => self.request_protocol(data),
|
|
State::Headers => self.request_headers(data),
|
|
State::ConnectWaitResponse => self.request_connect_wait_response(),
|
|
State::ConnectCheck => self.request_connect_check(),
|
|
State::ConnectProbeData => self.request_connect_probe_data(data),
|
|
State::BodyDetermine => self.request_body_determine(),
|
|
State::BodyChunkedData => self.request_body_chunked_data(data),
|
|
State::BodyChunkedLength => self.request_body_chunked_length(data),
|
|
State::BodyChunkedDataEnd => self.request_body_chunked_data_end(data),
|
|
State::BodyIdentity => self.request_body_identity(data),
|
|
State::Finalize => self.request_finalize(data),
|
|
// These are only used by response_state
|
|
_ => Err(HtpStatus::ERROR),
|
|
}
|
|
}
|
|
|
|
/// Handle the current state to be processed.
|
|
pub(crate) fn handle_response_state(&mut self, data: &mut ParserData) -> Result<()> {
|
|
match self.response_state {
|
|
State::None => Err(HtpStatus::ERROR),
|
|
State::Idle => self.response_idle(data),
|
|
State::Line => self.response_line(data),
|
|
State::Headers => self.response_headers(data),
|
|
State::BodyDetermine => self.response_body_determine(data),
|
|
State::BodyChunkedData => self.response_body_chunked_data(data),
|
|
State::BodyChunkedLength => self.response_body_chunked_length(data),
|
|
State::BodyChunkedDataEnd => self.response_body_chunked_data_end(data),
|
|
State::Finalize => self.response_finalize(data),
|
|
State::BodyIdentityStreamClose => self.response_body_identity_stream_close(data),
|
|
State::BodyIdentityCLKnown => self.response_body_identity_cl_known(data),
|
|
// These are only used by request_state
|
|
_ => Err(HtpStatus::ERROR),
|
|
}
|
|
}
|
|
|
|
/// Closes the connection associated with the supplied parser.
|
|
pub(crate) fn request_close(&mut self, timestamp: Option<OffsetDateTime>) {
|
|
// Update internal flags
|
|
if self.request_status != HtpStreamState::ERROR {
|
|
self.request_status = HtpStreamState::CLOSED
|
|
}
|
|
// Call the parsers one last time, which will allow them
|
|
// to process the events that depend on stream closure
|
|
self.request_data(ParserData::default(), timestamp);
|
|
}
|
|
|
|
/// Closes the connection associated with the supplied parser.
|
|
pub(crate) fn close(&mut self, timestamp: Option<OffsetDateTime>) {
|
|
// Close the underlying connection.
|
|
self.conn.close(timestamp);
|
|
// Update internal flags
|
|
if self.request_status != HtpStreamState::ERROR {
|
|
self.request_status = HtpStreamState::CLOSED
|
|
}
|
|
if self.response_status != HtpStreamState::ERROR {
|
|
self.response_status = HtpStreamState::CLOSED
|
|
}
|
|
// Call the parsers one last time, which will allow them
|
|
// to process the events that depend on stream closure
|
|
self.request_data(ParserData::default(), timestamp);
|
|
self.response_data(ParserData::default(), timestamp);
|
|
}
|
|
|
|
/// This function is most likely not used and/or not needed.
|
|
pub(crate) fn request_reset(&mut self) {
|
|
self.request_content_length = None;
|
|
self.request_body_data_left = None;
|
|
self.request_chunk_request_index = self.request_chunk_count;
|
|
}
|
|
|
|
/// Returns the number of bytes consumed from the current data chunks so far.
|
|
pub(crate) fn request_data_consumed(&self) -> usize {
|
|
self.request_bytes_consumed
|
|
}
|
|
|
|
/// Consume the given number of bytes from the ParserData and update
|
|
/// the internal counter for how many bytes consumed so far.
|
|
pub(crate) fn request_data_consume(&mut self, input: &ParserData, consumed: usize) {
|
|
input.consume(consumed);
|
|
self.request_bytes_consumed = input.consumed_len();
|
|
}
|
|
|
|
/// Unconsume the given number of bytes from the ParserData and update the
|
|
/// the internal counter for how many bytes are consumed.
|
|
/// If the requested number of bytes is larger than the number of bytes
|
|
/// already consumed then the parser will be unwound to the beginning.
|
|
pub(crate) fn request_data_unconsume(&mut self, input: &mut ParserData, unconsume: usize) {
|
|
input.unconsume(unconsume);
|
|
self.request_bytes_consumed = input.consumed_len();
|
|
}
|
|
|
|
/// Consume the given number of bytes from the ParserData and update
|
|
/// the internal counter for how many bytes consumed so far.
|
|
pub(crate) fn response_data_consume(&mut self, input: &ParserData, consumed: usize) {
|
|
input.consume(consumed);
|
|
self.response_bytes_consumed = input.consumed_len();
|
|
}
|
|
|
|
/// Unconsume the given number of bytes from the ParserData and update the
|
|
/// the internal counter for how many bytes are consumed.
|
|
/// If the requested number of bytes is larger than the number of bytes
|
|
/// already consumed then the parser will be unwound to the beginning.
|
|
pub(crate) fn response_data_unconsume(&mut self, input: &mut ParserData, unconsume: usize) {
|
|
input.unconsume(unconsume);
|
|
self.response_bytes_consumed = input.consumed_len();
|
|
}
|
|
|
|
/// Returns the number of bytes consumed from the most recent outbound data chunk. Normally, an invocation
|
|
/// of response_data() will consume all data from the supplied buffer, but there are circumstances
|
|
/// where only partial consumption is possible. In such cases DATA_OTHER will be returned.
|
|
/// Consumed bytes are no longer necessary, but the remainder of the buffer will be saved
|
|
/// for later.
|
|
pub(crate) fn response_data_consumed(&self) -> usize {
|
|
self.response_bytes_consumed
|
|
}
|
|
|
|
/// Opens connection.
|
|
pub(crate) fn open(
|
|
&mut self, client_addr: Option<IpAddr>, client_port: Option<u16>,
|
|
server_addr: Option<IpAddr>, server_port: Option<u16>, timestamp: Option<OffsetDateTime>,
|
|
) {
|
|
// Check connection parser state first.
|
|
if self.request_status != HtpStreamState::NEW || self.response_status != HtpStreamState::NEW
|
|
{
|
|
htp_error!(
|
|
self.logger,
|
|
HtpLogCode::CONNECTION_ALREADY_OPEN,
|
|
"Connection is already open"
|
|
);
|
|
return;
|
|
}
|
|
self.conn.open(
|
|
client_addr,
|
|
client_port,
|
|
server_addr,
|
|
server_port,
|
|
timestamp,
|
|
);
|
|
self.request_status = HtpStreamState::OPEN;
|
|
self.response_status = HtpStreamState::OPEN;
|
|
}
|
|
|
|
/// Set the user data.
|
|
pub(crate) fn set_user_data(&mut self, data: Box<dyn Any + 'static>) {
|
|
self.user_data = Some(data);
|
|
}
|
|
|
|
/// Get a reference to the user data.
|
|
pub(crate) fn user_data<T: 'static>(&self) -> Option<&T> {
|
|
self.user_data
|
|
.as_ref()
|
|
.and_then(|ud| ud.downcast_ref::<T>())
|
|
}
|
|
|
|
/// Initialize request parsing, change state to LINE,
|
|
/// and invoke all registered callbacks.
|
|
///
|
|
/// Returns HtpStatus::OK on success; HtpStatus::ERROR on error, HtpStatus::STOP if one of the
|
|
/// callbacks does not want to follow the transaction any more.
|
|
pub(crate) fn state_request_start(&mut self) -> Result<()> {
|
|
// Change state into request line parsing.
|
|
self.request_state = State::Line;
|
|
let req = self.request_mut();
|
|
if req.is_none() {
|
|
return Err(HtpStatus::ERROR);
|
|
}
|
|
req.unwrap().request_progress = HtpRequestProgress::LINE;
|
|
// Run hook REQUEST_START.
|
|
self.cfg
|
|
.hook_request_start
|
|
.clone()
|
|
.run_all(self, self.request_index())?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Change transaction state to HEADERS and invoke all
|
|
/// registered callbacks.
|
|
///
|
|
/// Returns HtpStatus::OK on success; HtpStatus::ERROR on error, HtpStatus::STOP if one of the
|
|
/// callbacks does not want to follow the transaction any more.
|
|
pub(crate) fn state_request_headers(&mut self, input: &mut ParserData) -> Result<()> {
|
|
// Finalize sending raw header data
|
|
self.request_receiver_finalize_clear(input)?;
|
|
// If we're in HTP_REQ_HEADERS that means that this is the
|
|
// first time we're processing headers in a request. Otherwise,
|
|
// we're dealing with trailing headers.
|
|
let req = self.request();
|
|
if req.is_none() {
|
|
return Err(HtpStatus::ERROR);
|
|
}
|
|
let request_progress = req.unwrap().request_progress;
|
|
if request_progress > HtpRequestProgress::HEADERS {
|
|
// Request trailers.
|
|
// Run hook HTP_REQUEST_TRAILER.
|
|
self.cfg
|
|
.hook_request_trailer
|
|
.clone()
|
|
.run_all(self, self.request_index())?;
|
|
// Completed parsing this request; finalize it now.
|
|
self.request_state = State::Finalize;
|
|
} else if request_progress >= HtpRequestProgress::LINE {
|
|
// Request headers.
|
|
// Did this request arrive in multiple data chunks?
|
|
let req = self.transactions.request_mut().unwrap();
|
|
if self.request_chunk_count != self.request_chunk_request_index {
|
|
req.flags.set(HtpFlags::MULTI_PACKET_HEAD)
|
|
}
|
|
req.process_request_headers()?;
|
|
// Run hook REQUEST_HEADERS.
|
|
#[cfg(test)]
|
|
self.cfg
|
|
.hook_request_headers
|
|
.clone()
|
|
.run_all(self, self.request_index())?;
|
|
self.request_initialize_decompressors()?;
|
|
|
|
// We still proceed if the request is invalid.
|
|
self.request_state = State::ConnectCheck;
|
|
} else {
|
|
htp_warn!(
|
|
self.logger,
|
|
HtpLogCode::RESPONSE_BODY_INTERNAL_ERROR,
|
|
format!(
|
|
"[Internal Error] Invalid tx progress: {:?}",
|
|
request_progress
|
|
)
|
|
);
|
|
return Err(HtpStatus::ERROR);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Change transaction state to PROTOCOL and invoke all
|
|
/// registered callbacks.
|
|
///
|
|
/// Returns HtpStatus::OK on success; HtpStatus::ERROR on error, HtpStatus::STOP if one of the
|
|
/// callbacks does not want to follow the transaction any more.
|
|
pub(crate) fn state_request_line(&mut self) -> Result<()> {
|
|
let req = self.request_mut();
|
|
if req.is_none() {
|
|
return Err(HtpStatus::ERROR);
|
|
}
|
|
req.unwrap().parse_request_line()?;
|
|
// Run hook REQUEST_LINE.
|
|
self.cfg
|
|
.hook_request_line
|
|
.clone()
|
|
.run_all(self, self.request_index())?;
|
|
let logger = self.logger.clone();
|
|
let req = self.request_mut().unwrap();
|
|
if let Some(parsed_uri) = req.parsed_uri.as_mut() {
|
|
let (partial_normalized_uri, complete_normalized_uri) =
|
|
parsed_uri.generate_normalized_uri(Some(logger));
|
|
req.partial_normalized_uri = partial_normalized_uri;
|
|
req.complete_normalized_uri = complete_normalized_uri;
|
|
}
|
|
// Move on to the next phase.
|
|
self.request_state = State::Protocol;
|
|
Ok(())
|
|
}
|
|
|
|
/// Advance state after processing request headers.
|
|
///
|
|
/// Returns HtpStatus::OK on success; HtpStatus::ERROR on error, HtpStatus::STOP
|
|
/// if one of the callbacks does not want to follow the transaction any more.
|
|
pub(crate) fn state_request_complete(&mut self, input: &mut ParserData) -> Result<()> {
|
|
let req = self.request_mut();
|
|
if req.is_none() {
|
|
return Err(HtpStatus::ERROR);
|
|
}
|
|
let req = req.unwrap();
|
|
if req.request_progress != HtpRequestProgress::COMPLETE {
|
|
// Finalize request body.
|
|
if req.request_has_body() {
|
|
self.request_body_data(None)?;
|
|
}
|
|
self.request_mut().unwrap().request_progress = HtpRequestProgress::COMPLETE;
|
|
// Run hook REQUEST_COMPLETE.
|
|
self.cfg
|
|
.hook_request_complete
|
|
.clone()
|
|
.run_all(self, self.request_index())?;
|
|
|
|
// Clear request data
|
|
self.request_receiver_finalize_clear(input)?;
|
|
}
|
|
// Determine what happens next, and remove this transaction from the parser.
|
|
self.request_state = if self.request().unwrap().is_protocol_0_9 {
|
|
State::IgnoreDataAfterHTTP09
|
|
} else {
|
|
State::Idle
|
|
};
|
|
// Check if the entire transaction is complete.
|
|
self.finalize(self.request_index())?;
|
|
self.request_next();
|
|
Ok(())
|
|
}
|
|
|
|
/// Determine if the transaction is complete and run any hooks.
|
|
fn finalize(&mut self, _tx_index: usize) -> Result<()> {
|
|
#[cfg(test)]
|
|
if let Some(tx) = self.tx(_tx_index) {
|
|
if !tx.is_complete() {
|
|
return Ok(());
|
|
}
|
|
// Disconnect transaction from the parser.
|
|
// Run hook TRANSACTION_COMPLETE.
|
|
self.cfg
|
|
.hook_transaction_complete
|
|
.clone()
|
|
.run_all(self, _tx_index)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Advance state to LINE, or BODY if http version is 0.9.
|
|
///
|
|
/// Returns HtpStatus::OK on success; HtpStatus::ERROR on error, HtpStatus::STOP
|
|
/// if one of the callbacks does not want to follow the transaction any more.
|
|
pub(crate) fn state_response_start(&mut self) -> Result<()> {
|
|
// Change state into response line parsing, except if we're following
|
|
// a HTTP/0.9 request (no status line or response headers).
|
|
let tx = self.response_mut();
|
|
if tx.is_none() {
|
|
return Err(HtpStatus::ERROR);
|
|
}
|
|
let tx = tx.unwrap();
|
|
|
|
if tx.is_protocol_0_9 {
|
|
tx.response_transfer_coding = HtpTransferCoding::Identity;
|
|
tx.response_content_encoding_processing = HtpContentEncoding::None;
|
|
tx.response_progress = HtpResponseProgress::BODY;
|
|
self.response_state = State::BodyIdentityStreamClose;
|
|
self.response_body_data_left = None
|
|
} else {
|
|
tx.response_progress = HtpResponseProgress::LINE;
|
|
self.response_state = State::Line
|
|
}
|
|
// Run hook RESPONSE_START.
|
|
self.cfg
|
|
.hook_response_start
|
|
.clone()
|
|
.run_all(self, self.response_index())?;
|
|
// If at this point we have no method and no uri and our status
|
|
// is still REQ_LINE, we likely have timed out request
|
|
// or a overly long request
|
|
let tx = self.response_mut().unwrap();
|
|
if tx.request_method.is_none()
|
|
&& tx.request_uri.is_none()
|
|
&& self.request_state == State::Line
|
|
{
|
|
htp_warn!(
|
|
self.logger,
|
|
HtpLogCode::REQUEST_LINE_INCOMPLETE,
|
|
"Request line incomplete"
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Advance state after processing response headers.
|
|
///
|
|
/// Returns HtpStatus::OK on success; HtpStatus::ERROR on error, HtpStatus::STOP
|
|
/// if one of the callbacks does not want to follow the transaction any more.
|
|
pub(crate) fn state_response_headers(&mut self, input: &mut ParserData) -> Result<()> {
|
|
// Finalize sending raw header data.
|
|
self.response_receiver_finalize_clear(input)?;
|
|
// Run hook RESPONSE_HEADERS.
|
|
#[cfg(test)]
|
|
self.cfg
|
|
.hook_response_headers
|
|
.clone()
|
|
.run_all(self, self.response_index())?;
|
|
self.response_initialize_decompressors()
|
|
}
|
|
|
|
/// Change transaction state to RESPONSE_LINE and invoke registered callbacks.
|
|
///
|
|
/// Returns HtpStatus::OK on success; HtpStatus::ERROR on error, HtpStatus::STOP
|
|
/// if one of the callbacks does not want to follow the transaction any more.
|
|
pub(crate) fn state_response_line(&mut self) -> Result<()> {
|
|
// Is the response line valid?
|
|
let tx = self.response_mut();
|
|
if tx.is_none() {
|
|
return Err(HtpStatus::ERROR);
|
|
}
|
|
let tx = tx.unwrap();
|
|
|
|
tx.validate_response_line();
|
|
#[cfg(test)]
|
|
let index = tx.index;
|
|
// Run hook HTP_RESPONSE_LINE
|
|
#[cfg(test)]
|
|
return self.cfg.hook_response_line.clone().run_all(self, index);
|
|
#[cfg(not(test))]
|
|
return Ok(());
|
|
}
|
|
|
|
/// Change transaction state to COMPLETE and invoke registered callbacks.
|
|
///
|
|
/// Returns HtpStatus::OK on success; HtpStatus::ERROR on error, HtpStatus::STOP
|
|
/// if one of the callbacks does not want to follow the transaction any more.
|
|
pub(crate) fn state_response_complete(&mut self, input: &mut ParserData) -> Result<()> {
|
|
let response_index = self.response_index();
|
|
let tx = self.response_mut();
|
|
if tx.is_none() {
|
|
return Err(HtpStatus::ERROR);
|
|
}
|
|
let tx = tx.unwrap();
|
|
if tx.response_progress != HtpResponseProgress::COMPLETE {
|
|
tx.response_progress = HtpResponseProgress::COMPLETE;
|
|
// Run the last RESPONSE_BODY_DATA HOOK, but only if there was a response body present.
|
|
if tx.response_transfer_coding != HtpTransferCoding::NoBody {
|
|
let _ = self.response_body_data(None);
|
|
}
|
|
// Run hook RESPONSE_COMPLETE.
|
|
self.cfg
|
|
.hook_response_complete
|
|
.clone()
|
|
.run_all(self, response_index)?;
|
|
|
|
// Clear the data receivers hook if any
|
|
self.response_receiver_finalize_clear(input)?;
|
|
}
|
|
// Check if we want to signal the caller to send request data
|
|
self.request_parser_check_waiting()?;
|
|
// Otherwise finalize the transaction
|
|
self.finalize(response_index)?;
|
|
self.response_next();
|
|
self.response_state = State::Idle;
|
|
Ok(())
|
|
}
|
|
|
|
/// Check if we had previously signalled the caller to give us response
|
|
/// data, and now we are ready to receive it
|
|
fn request_parser_check_waiting(&mut self) -> Result<()> {
|
|
// Check if the inbound parser is waiting on us. If it is, that means that
|
|
// there might be request data that the inbound parser hasn't consumed yet.
|
|
// If we don't stop parsing we might encounter a response without a request,
|
|
// which is why we want to return straight away before processing any data.
|
|
//
|
|
// This situation will occur any time the parser needs to see the server
|
|
// respond to a particular situation before it can decide how to proceed. For
|
|
// example, when a CONNECT is sent, different paths are used when it is accepted
|
|
// and when it is not accepted.
|
|
//
|
|
// It is not enough to check only in_status here. Because of pipelining, it's possible
|
|
// that many inbound transactions have been processed, and that the parser is
|
|
// waiting on a response that we have not seen yet.
|
|
if self.response_status == HtpStreamState::DATA_OTHER
|
|
&& self.response_index() == self.request_index()
|
|
{
|
|
return Err(HtpStatus::DATA_OTHER);
|
|
}
|
|
|
|
// Do we have a signal to yield to inbound processing at
|
|
// the end of the next transaction?
|
|
if self.response_data_other_at_tx_end {
|
|
// We do. Let's yield then.
|
|
self.response_data_other_at_tx_end = false;
|
|
if self.response_index() == self.request_index() {
|
|
return Err(HtpStatus::DATA_OTHER);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Remove the given transaction from the parser
|
|
pub(crate) fn remove_tx(&mut self, tx_id: usize) {
|
|
self.transactions.remove(tx_id);
|
|
}
|
|
}
|