retina_core/protocols/stream/http/parser.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
// modified from https://github.com/rusticata/rusticata/blob/master/src/http.rs
//! HTTP transaction parser.
//!
//! The HTTP transaction parser uses the [httparse](https://docs.rs/httparse/latest/httparse/) crate to parse HTTP request/responses. It handles HTTP pipelining, but does not currently support defragmenting message bodies.
//!
use super::transaction::{HttpRequest, HttpResponse};
use super::Http;
use crate::conntrack::pdu::L4Pdu;
use crate::protocols::stream::{
ConnParsable, ParseResult, ParsingState, ProbeResult, Session, SessionData,
};
use httparse::{Request, EMPTY_HEADER};
use std::collections::HashMap;
#[derive(Default, Debug)]
pub struct HttpParser {
/// Pending requests: maps session ID to HTTP transaction.
pending: HashMap<usize, Http>,
/// Current outstanding request ID (transaction depth).
current_trans: usize,
/// The current deepest transaction (total transactions ever seen).
cnt: usize,
}
impl HttpParser {
/// Process data segments from client to server
pub(crate) fn process_ctos(&mut self, data: &[u8]) -> ParseResult {
if let Ok(request) = HttpRequest::parse_from(data) {
let session_id = self.cnt;
let http = Http {
request,
response: HttpResponse::default(),
trans_depth: session_id,
};
self.cnt += 1;
self.pending.insert(session_id, http);
ParseResult::Continue(session_id)
} else {
// request continuation data or parse error.
// TODO: parse request continuation data
ParseResult::Skipped
}
}
/// Process data segments from server to client
pub(crate) fn process_stoc(&mut self, data: &[u8], pdu: &L4Pdu) -> ParseResult {
if let Ok(response) = HttpResponse::parse_from(data) {
if let Some(http) = self.pending.get_mut(&self.current_trans) {
http.response = response;
// TODO: Handle response continuation data instead of returning
// ParseResult::Done immediately on Response start-line
ParseResult::Done(self.current_trans)
} else {
log::warn!("HTTP response without oustanding request: {:?}", pdu.ctxt);
ParseResult::Skipped
}
} else {
// response continuation data or parse error.
// TODO: parse response continuation data
ParseResult::Skipped
}
}
}
impl ConnParsable for HttpParser {
fn parse(&mut self, pdu: &L4Pdu) -> ParseResult {
let offset = pdu.offset();
let length = pdu.length();
if length == 0 {
return ParseResult::Skipped;
}
if let Ok(data) = (pdu.mbuf_ref()).get_data_slice(offset, length) {
if pdu.dir {
self.process_ctos(data)
} else {
self.process_stoc(data, pdu)
}
} else {
log::warn!("Malformed packet on parse");
ParseResult::Skipped
}
}
fn probe(&self, pdu: &L4Pdu) -> ProbeResult {
// adapted from [the Rusticata HTTP parser](https://github.com/rusticata/rusticata/blob/master/src/http.rs)
// number of headers to parse at once
const NUM_OF_HEADERS: usize = 4;
if pdu.length() < 6 {
return ProbeResult::Unsure;
}
let offset = pdu.offset();
let length = pdu.length();
if let Ok(data) = (pdu.mbuf_ref()).get_data_slice(offset, length) {
// check if first characters match start of "request-line"
match &data[..4] {
b"OPTI" | b"GET " | b"HEAD" | b"POST" | b"PUT " | b"PATC" | b"COPY" | b"MOVE"
| b"DELE" | b"LINK" | b"UNLI" | b"TRAC" | b"WRAP" => (),
_ => return ProbeResult::NotForUs,
}
// try parsing request
let mut headers = [EMPTY_HEADER; NUM_OF_HEADERS];
let mut req = Request::new(&mut headers[..]);
let status = req.parse(data);
if let Err(e) = status {
if e != httparse::Error::TooManyHeaders {
log::trace!(
"data could be HTTP, but got error {:?} while parsing",
status
);
return ProbeResult::Unsure;
}
}
ProbeResult::Certain
} else {
log::warn!("Malformed packet");
ProbeResult::Error
}
}
fn remove_session(&mut self, session_id: usize) -> Option<Session> {
// Increment to next outstanding transaction in request order
self.current_trans = session_id + 1;
self.pending.remove(&session_id).map(|http| Session {
data: SessionData::Http(Box::new(http)),
id: session_id,
})
}
fn drain_sessions(&mut self) -> Vec<Session> {
self.pending
.drain()
.map(|(session_id, http)| Session {
data: SessionData::Http(Box::new(http)),
id: session_id,
})
.collect()
}
fn session_parsed_state(&self) -> ParsingState {
ParsingState::Parsing
}
}