huginn_net_http/http1/
process.rs1use super::parser as http1_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 crate::http2::parser as http2_parser;
10use crate::http2::process as http2_process;
11use tracing::debug;
12
13pub struct Http1Processor {
19 parser: http1_parser::Http1Parser,
20}
21
22impl Http1Processor {
23 pub fn new() -> Self {
24 Self { parser: http1_parser::Http1Parser::new() }
25 }
26}
27
28impl Default for Http1Processor {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl HttpProcessor for Http1Processor {
35 fn can_process_request(&self, data: &[u8]) -> bool {
36 if data.len() < 16 {
37 return false;
39 }
40
41 if http2_parser::is_http2_traffic(data) {
43 return false;
44 }
45
46 let data_str = String::from_utf8_lossy(data);
47 let first_line = data_str.lines().next().unwrap_or("");
48
49 let mut tokens = first_line.split_whitespace();
51 let (Some(method), Some(uri), Some(version)) =
52 (tokens.next(), tokens.next(), tokens.next())
53 else {
54 return false;
55 };
56 if tokens.next().is_some() {
57 return false;
58 }
59
60 let methods = [
62 "GET",
63 "POST",
64 "PUT",
65 "DELETE",
66 "HEAD",
67 "OPTIONS",
68 "PATCH",
69 "TRACE",
70 "CONNECT",
71 "PROPFIND",
72 "PROPPATCH",
73 "MKCOL",
74 "COPY",
75 "MOVE",
76 "LOCK",
77 "UNLOCK",
78 ];
79
80 methods.contains(&method)
82 && (version == "HTTP/1.0" || version == "HTTP/1.1")
83 && !uri.is_empty() }
85
86 fn can_process_response(&self, data: &[u8]) -> bool {
87 if data.len() < 12 {
88 return false;
90 }
91
92 if data.len() >= 9 && http2_process::looks_like_http2_response(data) {
94 return false;
95 }
96
97 let data_str = String::from_utf8_lossy(data);
98 let first_line = data_str.lines().next().unwrap_or("");
99
100 let mut tokens = first_line.splitn(3, ' ');
102 let (Some(version_str), Some(status_str)) = (tokens.next(), tokens.next()) else {
103 return false;
104 };
105
106 (version_str == "HTTP/1.0" || version_str == "HTTP/1.1")
108 && status_str.len() == 3
109 && status_str.chars().all(|c| c.is_ascii_digit())
110 }
111
112 fn has_complete_data(&self, data: &[u8]) -> bool {
113 has_complete_headers(data)
114 }
115
116 fn process_request(
117 &self,
118 data: &[u8],
119 ) -> Result<Option<ObservableHttpRequest>, HuginnNetHttpError> {
120 parse_http1_request(data, &self.parser)
121 }
122
123 fn process_response(
124 &self,
125 data: &[u8],
126 ) -> Result<Option<ObservableHttpResponse>, HuginnNetHttpError> {
127 parse_http1_response(data, &self.parser)
128 }
129
130 fn supported_version(&self) -> http::Version {
131 http::Version::V11 }
133
134 fn name(&self) -> &'static str {
135 "HTTP/1.x"
136 }
137}
138
139pub fn has_complete_headers(data: &[u8]) -> bool {
141 if data.len() < 4 {
143 return false;
144 }
145
146 for i in 0..data.len().saturating_sub(3) {
148 if data[i] == b'\r'
149 && data.get(i.saturating_add(1)) == Some(&b'\n')
150 && data.get(i.saturating_add(2)) == Some(&b'\r')
151 && data.get(i.saturating_add(3)) == Some(&b'\n')
152 {
153 return true;
154 }
155 }
156 false
157}
158
159fn convert_http1_request_to_observable(req: http1_parser::Http1Request) -> ObservableHttpRequest {
160 let lang = req
161 .accept_language
162 .and_then(http_languages::get_highest_quality_language);
163
164 let headers_in_order = convert_headers_to_http_format(&req.headers, true);
165 let headers_absent = build_absent_headers_from_new_parser(&req.headers, true);
166
167 ObservableHttpRequest {
168 matching: crate::observable::HttpRequestObservation {
169 version: req.version,
170 horder: headers_in_order,
171 habsent: headers_absent,
172 expsw: extract_traffic_classification(req.user_agent.as_deref()),
173 },
174 lang,
175 user_agent: req.user_agent.clone(),
176 headers: req.headers,
177 cookies: req.cookies.clone(),
178 referer: req.referer.clone(),
179 method: Some(req.method),
180 uri: Some(req.uri),
181 }
182}
183
184fn convert_http1_response_to_observable(
185 res: http1_parser::Http1Response,
186) -> ObservableHttpResponse {
187 let headers_in_order = convert_headers_to_http_format(&res.headers, false);
188 let headers_absent = build_absent_headers_from_new_parser(&res.headers, false);
189
190 ObservableHttpResponse {
191 matching: crate::observable::HttpResponseObservation {
192 version: res.version,
193 horder: headers_in_order,
194 habsent: headers_absent,
195 expsw: extract_traffic_classification(res.server.as_deref()),
196 },
197 headers: res.headers,
198 status_code: Some(res.status_code),
199 }
200}
201
202pub fn convert_headers_to_http_format(
205 headers: &[http_common::HttpHeader],
206 is_request: bool,
207) -> Vec<Header> {
208 let mut headers_in_order: Vec<Header> = Vec::with_capacity(headers.len());
209 let optional_list = if is_request {
210 http::request_optional_headers()
211 } else {
212 http::response_optional_headers()
213 };
214 let skip_value_list = if is_request {
215 http::request_skip_value_headers()
216 } else {
217 http::response_skip_value_headers()
218 };
219
220 for header in headers {
221 if optional_list.contains(&header.name.as_str()) {
222 headers_in_order.push(http::Header::new(&header.name).optional());
223 } else if skip_value_list.contains(&header.name.as_str()) {
224 headers_in_order.push(http::Header::new(&header.name));
225 } else {
226 headers_in_order
227 .push(Header::new(&header.name).with_optional_value(header.value.clone()));
228 }
229 }
230
231 headers_in_order
232}
233
234pub fn build_absent_headers_from_new_parser(
237 headers: &[http_common::HttpHeader],
238 is_request: bool,
239) -> Vec<Header> {
240 let common_list: Vec<&str> = if is_request {
241 http::request_common_headers()
242 } else {
243 http::response_common_headers()
244 };
245
246 let current_headers: Vec<String> = headers.iter().map(|h| h.name.to_lowercase()).collect();
247
248 let mut headers_absent: Vec<Header> = Vec::with_capacity(common_list.len());
249 for header in &common_list {
250 if !current_headers.contains(&header.to_lowercase()) {
251 headers_absent.push(Header::new(header));
252 }
253 }
254 headers_absent
255}
256
257pub fn parse_http1_request(
258 data: &[u8],
259 parser: &http1_parser::Http1Parser,
260) -> Result<Option<ObservableHttpRequest>, HuginnNetHttpError> {
261 match parser.parse_request(data) {
262 Ok(Some(req)) => {
263 let observable = convert_http1_request_to_observable(req);
264 Ok(Some(observable))
265 }
266 Ok(None) => {
267 debug!("Incomplete HTTP/1.x request data");
268 Ok(None)
269 }
270 Err(e) => {
271 debug!("Failed to parse HTTP/1.x request: {}", e);
272 Err(HuginnNetHttpError::Parse(format!("Failed to parse HTTP/1.x request: {e}")))
273 }
274 }
275}
276
277pub fn parse_http1_response(
278 data: &[u8],
279 parser: &http1_parser::Http1Parser,
280) -> Result<Option<ObservableHttpResponse>, HuginnNetHttpError> {
281 match parser.parse_response(data) {
282 Ok(Some(res)) => {
283 let observable = convert_http1_response_to_observable(res);
284 Ok(Some(observable))
285 }
286 Ok(None) => {
287 debug!("Incomplete HTTP/1.x response data");
288 Ok(None)
289 }
290 Err(e) => {
291 debug!("Failed to parse HTTP/1.x response: {}", e);
292 Err(HuginnNetHttpError::Parse(format!("Failed to parse HTTP/1.x response: {e}")))
293 }
294 }
295}
296
297fn extract_traffic_classification(value: Option<&str>) -> String {
298 value.unwrap_or("???").to_string()
299}
300
301pub fn looks_like_http1_response(data: &[u8]) -> bool {
303 if data.len() < 12 {
304 return false;
306 }
307
308 if data.len() >= 9 && http2_process::looks_like_http2_response(data) {
310 return false;
311 }
312
313 let data_str = String::from_utf8_lossy(data);
314 let first_line = data_str.lines().next().unwrap_or("");
315
316 let mut tokens = first_line.split_whitespace();
318 let (Some(version_str), Some(status_str)) = (tokens.next(), tokens.next()) else {
319 return false;
320 };
321
322 if version_str != "HTTP/1.0" && version_str != "HTTP/1.1" {
323 return false;
324 }
325
326 status_str.len() == 3 && status_str.chars().all(|c| c.is_ascii_digit())
327}