huginn_net_http/http2/
process.rs1use super::parser as http2_parser;
2use crate::error::HuginnNetHttpError;
3use crate::http;
4use crate::http::common as http_common;
5use crate::http::common::HttpProcessor;
6use crate::http::languages as http_languages;
7use crate::http::observable::{ObservableHttpRequest, ObservableHttpResponse};
8use crate::http::Header;
9use tracing::debug;
10
11pub struct Http2Processor {
17 parser: http2_parser::Http2Parser<'static>,
18}
19
20impl Http2Processor {
21 pub fn new() -> Self {
22 Self { parser: http2_parser::Http2Parser::new() }
23 }
24}
25
26impl Default for Http2Processor {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl HttpProcessor for Http2Processor {
33 fn can_process_request(&self, data: &[u8]) -> bool {
34 if data.len() < 24 {
36 return false;
38 }
39
40 http2_parser::is_http2_traffic(data)
42 }
43
44 fn can_process_response(&self, data: &[u8]) -> bool {
45 if data.len() < 9 {
47 return false;
49 }
50
51 let data_str = String::from_utf8_lossy(&data[..data.len().min(20)]);
53 if data_str.starts_with("HTTP/1.") {
54 return false;
55 }
56
57 looks_like_http2_response(data)
59 }
60
61 fn has_complete_data(&self, data: &[u8]) -> bool {
62 has_complete_data(data)
63 }
64
65 fn process_request(
66 &self,
67 data: &[u8],
68 ) -> Result<Option<ObservableHttpRequest>, HuginnNetHttpError> {
69 parse_http2_request(data, &self.parser)
70 }
71
72 fn process_response(
73 &self,
74 data: &[u8],
75 ) -> Result<Option<ObservableHttpResponse>, HuginnNetHttpError> {
76 parse_http2_response(data, &self.parser)
77 }
78
79 fn supported_version(&self) -> http::Version {
80 http::Version::V20
81 }
82
83 fn name(&self) -> &'static str {
84 "HTTP/2"
85 }
86}
87
88pub fn convert_http2_request_to_observable(
89 req: http2_parser::Http2Request,
90) -> ObservableHttpRequest {
91 let mut headers_map = std::collections::HashMap::new();
93 for header in &req.headers {
94 if let Some(ref value) = header.value {
95 headers_map.insert(header.name.to_lowercase(), value.as_str());
96 }
97 }
98
99 let lang = headers_map
100 .get("accept-language")
101 .and_then(|accept_language| {
102 http_languages::get_highest_quality_language(accept_language.to_string())
103 });
104
105 let headers_in_order = convert_http2_headers_to_http_format(&req.headers, true);
106 let headers_absent = build_absent_headers_from_http2(&req.headers, true);
107
108 let user_agent = headers_map.get("user-agent").map(|s| s.to_string());
109
110 ObservableHttpRequest {
111 matching: crate::observable::HttpRequestObservation {
112 version: req.version,
113 horder: headers_in_order,
114 habsent: headers_absent,
115 expsw: extract_traffic_classification(user_agent.as_deref()),
116 },
117 lang,
118 user_agent,
119 headers: req.headers,
120 cookies: req.cookies.clone(),
121 referer: req.referer.clone(),
122 method: Some(req.method),
123 uri: Some(req.path),
124 }
125}
126
127pub fn convert_http2_response_to_observable(
128 res: http2_parser::Http2Response,
129) -> ObservableHttpResponse {
130 let headers_in_order = convert_http2_headers_to_http_format(&res.headers, false);
131 let headers_absent = build_absent_headers_from_http2(&res.headers, false);
132
133 ObservableHttpResponse {
134 matching: crate::observable::HttpResponseObservation {
135 version: res.version,
136 horder: headers_in_order,
137 habsent: headers_absent,
138 expsw: extract_traffic_classification(res.server.as_deref()),
139 },
140 headers: res.headers,
141 status_code: Some(res.status),
142 }
143}
144
145fn convert_http2_headers_to_http_format(
146 headers: &[http_common::HttpHeader],
147 is_request: bool,
148) -> Vec<Header> {
149 let mut headers_in_order: Vec<Header> = Vec::with_capacity(headers.len());
150 let optional_list = if is_request {
151 http::request_optional_headers()
152 } else {
153 http::response_optional_headers()
154 };
155 let skip_value_list = if is_request {
156 http::request_skip_value_headers()
157 } else {
158 http::response_skip_value_headers()
159 };
160
161 for header in headers {
162 let header_name_lower = header.name.to_lowercase();
163 if optional_list.contains(&header_name_lower.as_str()) {
164 headers_in_order.push(http::Header::new(&header.name).optional());
165 } else if skip_value_list.contains(&header_name_lower.as_str()) {
166 headers_in_order.push(http::Header::new(&header.name));
167 } else {
168 headers_in_order
169 .push(http::Header::new(&header.name).with_optional_value(header.value.clone()));
170 }
171 }
172
173 headers_in_order
174}
175
176fn build_absent_headers_from_http2(
177 headers: &[http_common::HttpHeader],
178 is_request: bool,
179) -> Vec<Header> {
180 let common_list: Vec<&str> = if is_request {
181 http::request_common_headers()
182 } else {
183 http::response_common_headers()
184 };
185
186 let current_headers: Vec<String> = headers.iter().map(|h| h.name.to_lowercase()).collect();
187
188 let mut headers_absent: Vec<Header> = Vec::with_capacity(common_list.len());
189 for header in &common_list {
190 if !current_headers.contains(&header.to_lowercase()) {
191 headers_absent.push(http::Header::new(header));
192 }
193 }
194 headers_absent
195}
196
197pub fn parse_http2_request(
202 data: &[u8],
203 parser: &http2_parser::Http2Parser,
204) -> Result<Option<ObservableHttpRequest>, HuginnNetHttpError> {
205 match parser.parse_request(data) {
206 Ok(Some(req)) => {
207 let observable = convert_http2_request_to_observable(req);
208 Ok(Some(observable))
209 }
210 Ok(None) => {
211 debug!("Incomplete HTTP/2 request data");
212 Ok(None)
213 }
214 Err(e) => {
215 debug!("Failed to parse HTTP/2 request: {}", e);
216 Err(HuginnNetHttpError::Parse(format!("Failed to parse HTTP/2 request: {e}")))
217 }
218 }
219}
220
221fn parse_http2_response(
222 data: &[u8],
223 parser: &http2_parser::Http2Parser,
224) -> Result<Option<ObservableHttpResponse>, HuginnNetHttpError> {
225 match parser.parse_response(data) {
226 Ok(Some(res)) => {
227 let observable = convert_http2_response_to_observable(res);
228 Ok(Some(observable))
229 }
230 Ok(None) => {
231 debug!("Incomplete HTTP/2 response data");
232 Ok(None)
233 }
234 Err(e) => {
235 debug!("Failed to parse HTTP/2 response: {}", e);
236 Err(HuginnNetHttpError::Parse(format!("Failed to parse HTTP/2 response: {e}")))
237 }
238 }
239}
240
241pub fn extract_traffic_classification(value: Option<&str>) -> String {
242 value.unwrap_or("???").to_string()
243}
244
245pub fn looks_like_http2_response(data: &[u8]) -> bool {
247 if data.len() < 9 {
248 return false;
249 }
250
251 let frame_length = u32::from_be_bytes([0, data[0], data[1], data[2]]);
253 let frame_type = data[3];
254
255 if frame_length > 16384 {
257 return false;
258 }
259
260 matches!(frame_type, 0..=10)
263}
264
265pub fn has_complete_data(data: &[u8]) -> bool {
267 if data.starts_with(crate::http2_parser::HTTP2_CONNECTION_PREFACE) {
269 let frame_data = &data[crate::http2_parser::HTTP2_CONNECTION_PREFACE.len()..];
270 return has_complete_frames(frame_data);
271 }
272
273 has_complete_frames(data)
275}
276
277fn has_complete_frames(data: &[u8]) -> bool {
279 let mut remaining = data;
280
281 while remaining.len() >= 9 {
282 let length = u32::from_be_bytes([0, remaining[0], remaining[1], remaining[2]]);
284 let frame_type_byte = remaining[3];
285 let _flags = remaining[4];
286 let stream_id =
287 u32::from_be_bytes([remaining[5], remaining[6], remaining[7], remaining[8]])
288 & 0x7FFF_FFFF;
289
290 let frame_total_size = match usize::try_from(9_u32.saturating_add(length)) {
292 Ok(size) => size,
293 Err(_) => return false, };
295
296 if remaining.len() < frame_total_size {
297 return false; }
299
300 if frame_type_byte == 0x1 && stream_id > 0 {
302 return true;
304 }
305
306 remaining = &remaining[frame_total_size..];
308 }
309
310 false
311}