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
111pub 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 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 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 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 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 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 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}