Skip to main content

huginn_net_http/http2/
parser.rs

1use super::frames::{Http2Frame, Http2FrameType, HTTP2_CONNECTION_PREFACE};
2use crate::http;
3use crate::http::common::{HeaderSource, HttpCookie, HttpHeader, ParsingMetadata};
4use hpack_patched::Decoder;
5use std::cell::RefCell;
6use std::collections::HashMap;
7use std::time::Instant;
8
9#[derive(Debug, Clone, Default)]
10pub struct Http2Settings {
11    pub header_table_size: Option<u32>,
12    pub enable_push: Option<bool>,
13    pub max_concurrent_streams: Option<u32>,
14    pub initial_window_size: Option<u32>,
15    pub max_frame_size: Option<u32>,
16    pub max_header_list_size: Option<u32>,
17}
18
19#[derive(Debug, Clone)]
20pub struct Http2Stream {
21    pub stream_id: u32,
22    pub headers: Vec<HttpHeader>,
23    pub method: Option<String>,
24    pub path: Option<String>,
25    pub authority: Option<String>,
26    pub scheme: Option<String>,
27    pub status: Option<u16>,
28}
29
30pub struct Http2Config {
31    pub max_frame_size: u32,
32    pub max_streams: u32,
33    pub enable_hpack: bool,
34    pub strict_parsing: bool,
35}
36
37impl Default for Http2Config {
38    fn default() -> Self {
39        Self {
40            max_frame_size: 16384,
41            max_streams: 100,
42            enable_hpack: false,
43            strict_parsing: false,
44        }
45    }
46}
47
48#[derive(Debug, Clone)]
49pub struct Http2Request {
50    pub method: String,
51    pub path: String,
52    pub authority: Option<String>,
53    pub scheme: Option<String>,
54    pub version: http::Version,
55    pub headers: Vec<HttpHeader>,
56    pub cookies: Vec<HttpCookie>,
57    pub referer: Option<String>,
58    pub stream_id: u32,
59    pub parsing_metadata: ParsingMetadata,
60    pub frame_sequence: Vec<Http2FrameType>,
61    pub settings: Http2Settings,
62}
63
64#[derive(Debug, Clone)]
65pub struct Http2Response {
66    pub status: u16,
67    pub version: http::Version,
68    pub headers: Vec<HttpHeader>,
69    pub stream_id: u32,
70    pub parsing_metadata: ParsingMetadata,
71    pub frame_sequence: Vec<Http2FrameType>,
72    pub server: Option<String>,
73    pub content_type: Option<String>,
74}
75
76#[derive(Debug, Clone)]
77pub enum Http2ParseError {
78    InvalidPreface,
79    InvalidFrameHeader,
80    InvalidFrameLength(u32),
81    InvalidStreamId(u32),
82    FrameTooLarge(u32),
83    MissingRequiredHeaders,
84    InvalidPseudoHeader(String),
85    IncompleteFrame,
86    InvalidUtf8,
87    UnsupportedFeature(String),
88    HpackDecodingFailed,
89}
90
91impl std::fmt::Display for Http2ParseError {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        match self {
94            Self::InvalidPreface => write!(f, "Invalid HTTP/2 connection preface"),
95            Self::InvalidFrameHeader => write!(f, "Invalid HTTP/2 frame header"),
96            Self::InvalidFrameLength(len) => write!(f, "Invalid frame length: {len}"),
97            Self::InvalidStreamId(id) => write!(f, "Invalid stream ID: {id}"),
98            Self::FrameTooLarge(size) => write!(f, "Frame too large: {size} bytes"),
99            Self::MissingRequiredHeaders => write!(f, "Missing required pseudo-headers"),
100            Self::InvalidPseudoHeader(name) => write!(f, "Invalid pseudo-header: {name}"),
101            Self::IncompleteFrame => write!(f, "Incomplete HTTP/2 frame"),
102            Self::InvalidUtf8 => write!(f, "Invalid UTF-8 in HTTP/2 data"),
103            Self::UnsupportedFeature(feature) => write!(f, "Unsupported feature: {feature}"),
104            Self::HpackDecodingFailed => write!(f, "HPACK decoding failed"),
105        }
106    }
107}
108
109impl std::error::Error for Http2ParseError {}
110
111/// HTTP/2 Protocol Parser
112///
113/// Provides parsing capabilities for HTTP/2 requests and responses according to RFC 7540.
114/// Supports HPACK header compression and handles various frame types.
115///
116/// # Thread Safety
117///
118/// **This parser is NOT thread-safe.** Each thread should create its own instance.
119/// The internal HPACK decoder maintains state and uses `RefCell` for interior mutability.
120pub struct Http2Parser<'a> {
121    config: Http2Config,
122    hpack_decoder: RefCell<Decoder<'a>>,
123}
124
125impl<'a> Default for Http2Parser<'a> {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131impl<'a> Http2Parser<'a> {
132    pub fn new() -> Self {
133        Self { config: Http2Config::default(), hpack_decoder: RefCell::new(Decoder::new()) }
134    }
135
136    pub fn with_config(config: Http2Config) -> Self {
137        Self { config, hpack_decoder: RefCell::new(Decoder::new()) }
138    }
139
140    /// Parse HTTP/2 request from binary data
141    pub fn parse_request(&self, data: &[u8]) -> Result<Option<Http2Request>, Http2ParseError> {
142        let start_time = Instant::now();
143
144        if !self.has_http2_preface(data) {
145            return Err(Http2ParseError::InvalidPreface);
146        }
147
148        let frame_data = &data[HTTP2_CONNECTION_PREFACE.len()..];
149        let frames = self.parse_frames(frame_data)?;
150
151        if frames.is_empty() {
152            return Ok(None);
153        }
154
155        let Some(stream_id) = self.find_primary_stream(&frames) else {
156            return Ok(None);
157        };
158        let stream = self.build_stream(stream_id, &frames)?;
159
160        let method = stream
161            .method
162            .ok_or(Http2ParseError::MissingRequiredHeaders)?;
163        let path = stream.path.ok_or(Http2ParseError::MissingRequiredHeaders)?;
164
165        let parsing_time = start_time.elapsed().as_nanos() as u64;
166        let frame_sequence: Vec<Http2FrameType> =
167            frames.iter().map(|f| f.frame_type.clone()).collect();
168
169        let mut headers = Vec::new();
170        let mut headers_map = HashMap::new();
171        let mut referer: Option<String> = None;
172        let mut cookie_headers: Vec<&HttpHeader> = Vec::new();
173
174        for header in &stream.headers {
175            let header_name_lower = header.name.to_lowercase();
176
177            if header_name_lower == "cookie" {
178                cookie_headers.push(header);
179            } else if header_name_lower == "referer" {
180                if let Some(ref value) = header.value {
181                    referer = Some(value.clone());
182                }
183            } else {
184                if let Some(ref value) = header.value {
185                    headers_map.insert(header_name_lower, value.clone());
186                }
187                headers.push(header.clone());
188            }
189        }
190
191        let cookies = self.parse_cookies_from_headers(&cookie_headers);
192
193        let metadata = ParsingMetadata {
194            header_count: headers.len(),
195            duplicate_headers: Vec::new(),
196            case_variations: HashMap::new(),
197            parsing_time_ns: parsing_time,
198            has_malformed_headers: false,
199            request_line_length: 0,
200            total_headers_length: headers
201                .iter()
202                .map(|h| {
203                    h.name
204                        .len()
205                        .saturating_add(h.value.as_ref().map_or(0, |v| v.len()))
206                })
207                .sum(),
208        };
209
210        Ok(Some(Http2Request {
211            method,
212            path,
213            authority: stream.authority,
214            scheme: stream.scheme,
215            version: http::Version::V20,
216            headers,
217            cookies,
218            referer,
219            stream_id,
220            parsing_metadata: metadata,
221            frame_sequence,
222            settings: self.extract_settings(&frames),
223        }))
224    }
225
226    /// Parse HTTP/2 response from binary data
227    pub fn parse_response(&self, data: &[u8]) -> Result<Option<Http2Response>, Http2ParseError> {
228        let start_time = Instant::now();
229
230        let frames = self.parse_frames(data)?;
231
232        if frames.is_empty() {
233            return Ok(None);
234        }
235
236        let Some(stream_id) = self.find_primary_stream(&frames) else {
237            return Ok(None);
238        };
239        let stream = self.build_stream(stream_id, &frames)?;
240
241        let status = stream
242            .status
243            .ok_or(Http2ParseError::MissingRequiredHeaders)?;
244
245        let parsing_time = start_time.elapsed().as_nanos() as u64;
246        let frame_sequence: Vec<Http2FrameType> =
247            frames.iter().map(|f| f.frame_type.clone()).collect();
248
249        let mut headers_map = HashMap::new();
250        for header in &stream.headers {
251            if let Some(ref value) = header.value {
252                headers_map.insert(header.name.to_lowercase(), value.clone());
253            }
254        }
255
256        let metadata = ParsingMetadata {
257            header_count: stream.headers.len(),
258            duplicate_headers: Vec::new(),
259            case_variations: HashMap::new(),
260            parsing_time_ns: parsing_time,
261            has_malformed_headers: false,
262            request_line_length: 0,
263            total_headers_length: stream
264                .headers
265                .iter()
266                .map(|h| {
267                    h.name
268                        .len()
269                        .saturating_add(h.value.as_ref().map_or(0, |v| v.len()))
270                })
271                .sum(),
272        };
273
274        Ok(Some(Http2Response {
275            status,
276            version: http::Version::V20,
277            headers: stream.headers,
278            stream_id,
279            parsing_metadata: metadata,
280            frame_sequence,
281            server: headers_map.get("server").cloned(),
282            content_type: headers_map.get("content-type").cloned(),
283        }))
284    }
285
286    fn has_http2_preface(&self, data: &[u8]) -> bool {
287        data.starts_with(HTTP2_CONNECTION_PREFACE)
288    }
289
290    /// Parse HTTP/2 frames from raw data
291    ///
292    /// Parses all frames from the given data, handling connection preface if present.
293    /// Returns a vector of parsed frames or an error if parsing fails.
294    pub fn parse_frames(&self, data: &[u8]) -> Result<Vec<Http2Frame>, Http2ParseError> {
295        let mut frames = Vec::new();
296        let mut remaining = data;
297
298        while remaining.len() >= 9 {
299            let frame_length = u32::from_be_bytes([0, remaining[0], remaining[1], remaining[2]]);
300            let frame_total_size = match usize::try_from(9_u32.saturating_add(frame_length)) {
301                Ok(size) => size,
302                Err(_) => break,
303            };
304
305            if remaining.len() < frame_total_size {
306                break;
307            }
308
309            match self.parse_single_frame(remaining) {
310                Ok((rest, frame)) => {
311                    frames.push(frame);
312                    remaining = rest;
313                }
314                Err(_) => {
315                    break;
316                }
317            }
318        }
319
320        Ok(frames)
321    }
322
323    /// Parse HTTP/2 frames from raw data and return the number of bytes consumed
324    ///
325    /// This is a convenience method that returns both the parsed frames and the number of bytes
326    /// consumed from the input buffer. Useful for tracking parsing progress when processing
327    /// incremental data.
328    ///
329    /// # Parameters
330    /// - `data`: Raw HTTP/2 frame data (may include connection preface)
331    ///
332    /// # Returns
333    /// - `Ok((frames, bytes_consumed))` on success
334    /// - `Err(Http2ParseError)` on parsing failure
335    ///
336    /// # Example
337    /// ```no_run
338    /// use huginn_net_http::http2_parser::Http2Parser;
339    ///
340    /// let parser = Http2Parser::new();
341    /// let data = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n\x00\x00\x06\x04\x00\x00\x00\x00\x00";
342    /// match parser.parse_frames_with_offset(data) {
343    ///     Ok((frames, bytes_consumed)) => {
344    ///         println!("Parsed {} frames, consumed {} bytes", frames.len(), bytes_consumed);
345    ///     }
346    ///     Err(e) => eprintln!("Parsing error: {:?}", e),
347    /// }
348    /// ```
349    pub fn parse_frames_with_offset(
350        &self,
351        data: &[u8],
352    ) -> Result<(Vec<Http2Frame>, usize), Http2ParseError> {
353        let frames = self.parse_frames(data)?;
354        let bytes_consumed: usize = frames.iter().map(|f| f.total_size()).sum();
355        Ok((frames, bytes_consumed))
356    }
357
358    /// Parse HTTP/2 frames from raw data, automatically skipping the connection preface if present
359    ///
360    /// This is a convenience method that handles the HTTP/2 connection preface automatically,
361    /// making it easier to parse frames from raw connection data.
362    ///
363    /// # Parameters
364    /// - `data`: Raw HTTP/2 frame data (may include connection preface)
365    ///
366    /// # Returns
367    /// - `Ok((frames, bytes_consumed))` on success, where `bytes_consumed` includes the preface if present
368    /// - `Err(Http2ParseError)` on parsing failure
369    ///
370    /// # Example
371    /// ```no_run
372    /// use huginn_net_http::http2_parser::Http2Parser;
373    ///
374    /// let parser = Http2Parser::new();
375    /// let data = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n\x00\x00\x06\x04\x00\x00\x00\x00\x00";
376    /// match parser.parse_frames_skip_preface(data) {
377    ///     Ok((frames, bytes_consumed)) => {
378    ///         println!("Parsed {} frames, consumed {} bytes (including preface)", frames.len(), bytes_consumed);
379    ///     }
380    ///     Err(e) => eprintln!("Parsing error: {:?}", e),
381    /// }
382    /// ```
383    pub fn parse_frames_skip_preface(
384        &self,
385        data: &[u8],
386    ) -> Result<(Vec<Http2Frame>, usize), Http2ParseError> {
387        let start = if data.starts_with(HTTP2_CONNECTION_PREFACE) {
388            HTTP2_CONNECTION_PREFACE.len()
389        } else {
390            0
391        };
392        let (frames, bytes_consumed) = self.parse_frames_with_offset(&data[start..])?;
393        Ok((frames, start.saturating_add(bytes_consumed)))
394    }
395
396    fn parse_single_frame<'b>(
397        &self,
398        data: &'b [u8],
399    ) -> Result<(&'b [u8], Http2Frame), Http2ParseError> {
400        if data.len() < 9 {
401            return Err(Http2ParseError::IncompleteFrame);
402        }
403
404        let length = u32::from_be_bytes([0, data[0], data[1], data[2]]);
405        let frame_type_byte = data[3];
406        let flags = data[4];
407        let stream_id = u32::from_be_bytes([data[5], data[6], data[7], data[8]]) & 0x7FFF_FFFF;
408
409        if length > self.config.max_frame_size {
410            return Err(Http2ParseError::FrameTooLarge(length));
411        }
412
413        let frame_total_size = match usize::try_from(9_u32.saturating_add(length)) {
414            Ok(size) => size,
415            Err(_) => return Err(Http2ParseError::FrameTooLarge(length)),
416        };
417
418        if data.len() < frame_total_size {
419            return Err(Http2ParseError::IncompleteFrame);
420        }
421
422        let payload_start = 9;
423        let payload_end = frame_total_size;
424        let payload = data[payload_start..payload_end].to_vec();
425
426        let frame = Http2Frame {
427            frame_type: Http2FrameType::from(frame_type_byte),
428            stream_id,
429            flags,
430            payload,
431            length,
432        };
433
434        Ok((&data[payload_end..], frame))
435    }
436
437    fn find_primary_stream(&self, frames: &[Http2Frame]) -> Option<u32> {
438        for frame in frames {
439            if frame.stream_id > 0 && frame.frame_type == Http2FrameType::Headers {
440                return Some(frame.stream_id);
441            }
442        }
443        None
444    }
445
446    fn build_stream(
447        &self,
448        stream_id: u32,
449        frames: &[Http2Frame],
450    ) -> Result<Http2Stream, Http2ParseError> {
451        let mut headers = Vec::new();
452        let mut method = None;
453        let mut path = None;
454        let mut authority = None;
455        let mut scheme = None;
456        let mut status = None;
457
458        let stream_frames: Vec<&Http2Frame> =
459            frames.iter().filter(|f| f.stream_id == stream_id).collect();
460
461        for frame in stream_frames {
462            match frame.frame_type {
463                Http2FrameType::Headers | Http2FrameType::Continuation => {
464                    let frame_headers = self.parse_headers_payload(&frame.payload)?;
465                    for header in frame_headers {
466                        match header.name.as_str() {
467                            ":method" => method = Some(header.value.clone().unwrap_or_default()),
468                            ":path" => path = Some(header.value.clone().unwrap_or_default()),
469                            ":authority" => {
470                                authority = Some(header.value.clone().unwrap_or_default())
471                            }
472                            ":scheme" => scheme = Some(header.value.clone().unwrap_or_default()),
473                            ":status" => {
474                                status = header.value.as_ref().and_then(|v| v.parse().ok())
475                            }
476                            _ => headers.push(header),
477                        }
478                    }
479                }
480                _ => {}
481            }
482        }
483
484        Ok(Http2Stream { stream_id, headers, method, path, authority, scheme, status })
485    }
486
487    fn parse_headers_payload(&self, payload: &[u8]) -> Result<Vec<HttpHeader>, Http2ParseError> {
488        let headers = self
489            .hpack_decoder
490            .borrow_mut()
491            .decode(payload)
492            .map_err(|_| Http2ParseError::HpackDecodingFailed)?;
493
494        let mut http_headers = Vec::new();
495
496        for (position, (name, value)) in headers.iter().enumerate() {
497            let name_str = String::from_utf8_lossy(name).to_string();
498            let value_str = String::from_utf8_lossy(value);
499            let value_opt = if value_str.is_empty() {
500                None
501            } else {
502                Some(value_str.to_string())
503            };
504
505            http_headers.push(HttpHeader {
506                name: name_str,
507                value: value_opt,
508                position,
509                source: HeaderSource::Http2Header,
510            });
511        }
512
513        Ok(http_headers)
514    }
515
516    fn extract_settings(&self, frames: &[Http2Frame]) -> Http2Settings {
517        let mut settings = Http2Settings::default();
518
519        for frame in frames {
520            if frame.frame_type == Http2FrameType::Settings {
521                let payload = &frame.payload;
522                for chunk in payload.chunks_exact(6) {
523                    if chunk.len() == 6 {
524                        let id = u16::from_be_bytes([chunk[0], chunk[1]]);
525                        let value = u32::from_be_bytes([chunk[2], chunk[3], chunk[4], chunk[5]]);
526
527                        match id {
528                            1 => settings.header_table_size = Some(value),
529                            2 => settings.enable_push = Some(value != 0),
530                            3 => settings.max_concurrent_streams = Some(value),
531                            4 => settings.initial_window_size = Some(value),
532                            5 => settings.max_frame_size = Some(value),
533                            6 => settings.max_header_list_size = Some(value),
534                            _ => {}
535                        }
536                    }
537                }
538            }
539        }
540
541        settings
542    }
543
544    /// HTTP/2 cookie parsing - handles multiple cookie headers according to RFC 7540
545    pub fn parse_cookies_from_headers(&self, cookie_headers: &[&HttpHeader]) -> Vec<HttpCookie> {
546        let mut cookies = Vec::new();
547        let mut position = 0;
548
549        for header in cookie_headers {
550            if let Some(ref cookie_value) = header.value {
551                for cookie_str in cookie_value.split(';') {
552                    let cookie_str = cookie_str.trim();
553                    if cookie_str.is_empty() {
554                        continue;
555                    }
556
557                    if let Some(eq_pos) = cookie_str.find('=') {
558                        let name = cookie_str[..eq_pos].trim().to_string();
559                        let value = Some(
560                            cookie_str
561                                .get(eq_pos.saturating_add(1)..)
562                                .unwrap_or("")
563                                .trim()
564                                .to_string(),
565                        );
566                        cookies.push(HttpCookie { name, value, position });
567                    } else {
568                        cookies.push(HttpCookie {
569                            name: cookie_str.to_string(),
570                            value: None,
571                            position,
572                        });
573                    }
574                    position = position.saturating_add(1);
575                }
576            }
577        }
578
579        cookies
580    }
581}
582
583pub fn is_http2_traffic(data: &[u8]) -> bool {
584    data.starts_with(HTTP2_CONNECTION_PREFACE)
585}