Skip to main content

retina_core/protocols/stream/http/
parser.rs

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