Skip to main content

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