1use crate::http;
2use crate::http_common::{HeaderSource, HttpCookie, HttpHeader, ParsingMetadata};
3use std::collections::HashMap;
4use std::time::Instant;
5
6pub struct Http1Config {
7 pub max_headers: usize,
8 pub max_request_line_length: usize,
9 pub max_header_length: usize,
10 pub preserve_header_order: bool,
11 pub parse_cookies: bool,
12 pub strict_parsing: bool,
13}
14
15impl Default for Http1Config {
16 fn default() -> Self {
17 Self {
18 max_headers: 100,
19 max_request_line_length: 8192,
20 max_header_length: 8192,
21 preserve_header_order: true,
22 parse_cookies: true,
23 strict_parsing: false,
24 }
25 }
26}
27
28#[derive(Debug, Clone)]
29pub struct Http1Request {
30 pub method: String,
31 pub uri: String,
32 pub version: http::Version,
33 pub headers: Vec<HttpHeader>,
34 pub cookies: Vec<HttpCookie>,
35 pub referer: Option<String>,
36 pub content_length: Option<usize>,
37 pub transfer_encoding: Option<String>,
38 pub connection: Option<String>,
39 pub host: Option<String>,
40 pub user_agent: Option<String>,
41 pub accept_language: Option<String>,
42 pub raw_request_line: String,
43 pub parsing_metadata: ParsingMetadata,
44}
45
46#[derive(Debug, Clone)]
47pub struct Http1Response {
48 pub version: http::Version,
49 pub status_code: u16,
50 pub reason_phrase: String,
51 pub headers: Vec<HttpHeader>,
52 pub content_length: Option<usize>,
53 pub transfer_encoding: Option<String>,
54 pub server: Option<String>,
55 pub content_type: Option<String>,
56 pub raw_status_line: String,
57 pub parsing_metadata: ParsingMetadata,
58}
59
60#[derive(Debug, Clone)]
61pub enum Http1ParseError {
62 InvalidRequestLine(String),
63 InvalidStatusLine(String),
64 InvalidVersion(String),
65 InvalidMethod(String),
66 InvalidStatusCode(String),
67 HeaderTooLong(usize),
68 TooManyHeaders(usize),
69 MalformedHeader(String),
70 IncompleteData,
71 InvalidUtf8,
72}
73
74impl std::fmt::Display for Http1ParseError {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 match self {
77 Self::InvalidRequestLine(line) => write!(f, "Invalid request line: {line}"),
78 Self::InvalidStatusLine(line) => write!(f, "Invalid status line: {line}"),
79 Self::InvalidVersion(version) => write!(f, "Invalid HTTP version: {version}"),
80 Self::InvalidMethod(method) => write!(f, "Invalid HTTP method: {method}"),
81 Self::InvalidStatusCode(code) => write!(f, "Invalid status code: {code}"),
82 Self::HeaderTooLong(len) => write!(f, "Header too long: {len} bytes"),
83 Self::TooManyHeaders(count) => write!(f, "Too many headers: {count}"),
84 Self::MalformedHeader(header) => write!(f, "Malformed header: {header}"),
85 Self::IncompleteData => write!(f, "Incomplete HTTP data"),
86 Self::InvalidUtf8 => write!(f, "Invalid UTF-8 in HTTP data"),
87 }
88 }
89}
90
91impl std::error::Error for Http1ParseError {}
92
93pub struct Http1Parser {
103 config: Http1Config,
104}
105
106impl Http1Parser {
107 pub fn new() -> Self {
108 Self {
109 config: Http1Config::default(),
110 }
111 }
112 pub fn parse_request(&self, data: &[u8]) -> Result<Option<Http1Request>, Http1ParseError> {
113 let start_time = Instant::now();
114
115 let data_str = std::str::from_utf8(data).map_err(|_| Http1ParseError::InvalidUtf8)?;
116
117 if !data_str.contains("\r\n\r\n") && !data_str.contains("\n\n") {
118 return Ok(None);
119 }
120 let lines: Vec<&str> = if data_str.contains("\r\n") {
121 data_str.split("\r\n").collect()
122 } else {
123 data_str.split('\n').collect()
124 };
125
126 if lines.is_empty() {
127 return Err(Http1ParseError::IncompleteData);
128 }
129
130 let (method, uri, version) = self.parse_request_line(lines[0])?;
131
132 let header_end = lines
133 .iter()
134 .position(|line| line.is_empty())
135 .unwrap_or(lines.len());
136
137 let header_lines = &lines[1..header_end];
138 let (all_headers, parsing_metadata) = self.parse_headers(header_lines)?;
139
140 let mut headers = Vec::new();
141 let mut headers_map = HashMap::new();
142 let mut cookie_header_value: Option<String> = None;
143 let mut referer: Option<String> = None;
144
145 for header in all_headers {
146 let header_name_lower = header.name.to_lowercase();
147
148 if header_name_lower == "cookie" {
149 if let Some(ref value) = header.value {
150 cookie_header_value = Some(value.clone());
151 }
152 } else if header_name_lower == "referer" {
153 if let Some(ref value) = header.value {
154 referer = Some(value.clone());
155 }
156 } else {
157 if let Some(ref value) = header.value {
158 headers_map
159 .entry(header_name_lower)
160 .or_insert(value.clone());
161 }
162 headers.push(header);
163 }
164 }
165
166 let cookies = if self.config.parse_cookies {
167 if let Some(cookie_header) = cookie_header_value {
168 self.parse_cookies(&cookie_header)
169 } else {
170 Vec::new()
171 }
172 } else {
173 Vec::new()
174 };
175
176 let content_length = headers_map
177 .get("content-length")
178 .and_then(|v| v.parse().ok());
179
180 let parsing_time = start_time.elapsed().as_nanos() as u64;
181
182 let mut final_metadata = parsing_metadata;
183 final_metadata.parsing_time_ns = parsing_time;
184 final_metadata.request_line_length = lines[0].len();
185
186 Ok(Some(Http1Request {
187 method,
188 uri,
189 version,
190 headers,
191 cookies,
192 referer,
193 content_length,
194 transfer_encoding: headers_map.get("transfer-encoding").cloned(),
195 connection: headers_map.get("connection").cloned(),
196 host: headers_map.get("host").cloned(),
197 user_agent: headers_map.get("user-agent").cloned(),
198 accept_language: headers_map.get("accept-language").cloned(),
199 raw_request_line: lines[0].to_string(),
200 parsing_metadata: final_metadata,
201 }))
202 }
203
204 pub fn parse_response(&self, data: &[u8]) -> Result<Option<Http1Response>, Http1ParseError> {
205 let start_time = Instant::now();
206
207 let data_str = std::str::from_utf8(data).map_err(|_| Http1ParseError::InvalidUtf8)?;
208
209 if !data_str.contains("\r\n\r\n") && !data_str.contains("\n\n") {
210 return Ok(None);
211 }
212 let lines: Vec<&str> = if data_str.contains("\r\n") {
213 data_str.split("\r\n").collect()
214 } else {
215 data_str.split('\n').collect()
216 };
217
218 if lines.is_empty() {
219 return Err(Http1ParseError::IncompleteData);
220 }
221
222 let (version, status_code, reason_phrase) = self.parse_status_line(lines[0])?;
223
224 let header_end = lines
225 .iter()
226 .position(|line| line.is_empty())
227 .unwrap_or(lines.len());
228
229 let header_lines = &lines[1..header_end];
230 let (headers, parsing_metadata) = self.parse_headers(header_lines)?;
231
232 let mut headers_map = HashMap::new();
233 for header in &headers {
234 if let Some(ref value) = header.value {
235 headers_map
236 .entry(header.name.to_lowercase())
237 .or_insert(value.clone());
238 }
239 }
240
241 let content_length = headers_map
242 .get("content-length")
243 .and_then(|v| v.parse().ok());
244
245 let parsing_time = start_time.elapsed().as_nanos() as u64;
246
247 let mut final_metadata = parsing_metadata;
248 final_metadata.parsing_time_ns = parsing_time;
249
250 Ok(Some(Http1Response {
251 version,
252 status_code,
253 reason_phrase,
254 headers,
255 content_length,
256 transfer_encoding: headers_map.get("transfer-encoding").cloned(),
257 server: headers_map.get("server").cloned(),
258 content_type: headers_map.get("content-type").cloned(),
259 raw_status_line: lines[0].to_string(),
260 parsing_metadata: final_metadata,
261 }))
262 }
263
264 fn parse_request_line(
265 &self,
266 line: &str,
267 ) -> Result<(String, String, http::Version), Http1ParseError> {
268 if line.len() > self.config.max_request_line_length {
269 return Err(Http1ParseError::InvalidRequestLine(format!(
270 "Request line too long: {} bytes",
271 line.len()
272 )));
273 }
274
275 let parts: Vec<&str> = line.split_whitespace().collect();
276 if parts.len() != 3 {
277 return Err(Http1ParseError::InvalidRequestLine(line.to_string()));
278 }
279
280 let method = parts[0].to_string();
281 let uri = parts[1].to_string();
282 let version = http::Version::parse(parts[2])
283 .ok_or_else(|| Http1ParseError::InvalidVersion(parts[2].to_string()))?;
284
285 if !matches!(version, http::Version::V10 | http::Version::V11) {
287 return Err(Http1ParseError::InvalidVersion(parts[2].to_string()));
288 }
289
290 if !self.is_valid_method(&method) {
291 return Err(Http1ParseError::InvalidMethod(method));
292 }
293
294 Ok((method, uri, version))
295 }
296
297 fn parse_status_line(
298 &self,
299 line: &str,
300 ) -> Result<(http::Version, u16, String), Http1ParseError> {
301 let parts: Vec<&str> = line.splitn(3, ' ').collect();
302 if parts.len() < 2 {
303 return Err(Http1ParseError::InvalidStatusLine(line.to_string()));
304 }
305
306 let version = http::Version::parse(parts[0])
307 .ok_or_else(|| Http1ParseError::InvalidVersion(parts[0].to_string()))?;
308
309 if !matches!(version, http::Version::V10 | http::Version::V11) {
311 return Err(Http1ParseError::InvalidVersion(parts[0].to_string()));
312 }
313
314 let status_code: u16 = parts[1]
315 .parse()
316 .map_err(|_| Http1ParseError::InvalidStatusCode(parts[1].to_string()))?;
317 let reason_phrase = parts.get(2).unwrap_or(&"").to_string();
318
319 Ok((version, status_code, reason_phrase))
320 }
321
322 fn parse_headers(
323 &self,
324 lines: &[&str],
325 ) -> Result<(Vec<HttpHeader>, ParsingMetadata), Http1ParseError> {
326 if lines.len() > self.config.max_headers {
327 return Err(Http1ParseError::TooManyHeaders(lines.len()));
328 }
329
330 let mut headers = Vec::new();
331 let mut duplicate_headers = Vec::new();
332 let mut case_variations: HashMap<String, Vec<String>> = HashMap::new();
333 let mut has_malformed = false;
334 let mut total_length: usize = 0;
335
336 for (position, line) in lines.iter().enumerate() {
337 if line.is_empty() {
338 break;
339 }
340
341 total_length = total_length.saturating_add(line.len());
342
343 if line.len() > self.config.max_header_length {
344 return Err(Http1ParseError::HeaderTooLong(line.len()));
345 }
346
347 if let Some(colon_pos) = line.find(':') {
348 let name = line[..colon_pos].trim().to_string();
349 let value = line
350 .get(colon_pos.saturating_add(1)..)
351 .map(|v| v.trim().to_string());
352
353 if name.is_empty() {
354 has_malformed = true;
355 if self.config.strict_parsing {
356 return Err(Http1ParseError::MalformedHeader(line.to_string()));
357 }
358 continue;
359 }
360
361 let name_lower = name.to_lowercase();
362 case_variations
363 .entry(name_lower.clone())
364 .or_default()
365 .push(name.clone());
366 if headers
367 .iter()
368 .any(|h: &HttpHeader| h.name.to_lowercase() == name_lower)
369 {
370 duplicate_headers.push(name_lower.clone());
371 }
372
373 headers.push(HttpHeader {
374 name,
375 value,
376 position,
377 source: HeaderSource::Http1Line,
378 });
379 } else {
380 has_malformed = true;
381 if self.config.strict_parsing {
382 return Err(Http1ParseError::MalformedHeader(line.to_string()));
383 }
384 }
385 }
386
387 let metadata = ParsingMetadata {
388 header_count: headers.len(),
389 duplicate_headers,
390 case_variations,
391 parsing_time_ns: 0,
392 has_malformed_headers: has_malformed,
393 request_line_length: 0,
394 total_headers_length: total_length,
395 };
396
397 Ok((headers, metadata))
398 }
399
400 pub fn parse_cookies(&self, cookie_header: &str) -> Vec<HttpCookie> {
402 let mut cookies = Vec::new();
403 let mut position = 0;
404
405 for cookie_str in cookie_header.split(';') {
406 let cookie_str = cookie_str.trim();
407 if cookie_str.is_empty() {
408 continue;
409 }
410
411 if let Some(eq_pos) = cookie_str.find('=') {
412 let name = cookie_str[..eq_pos].trim().to_string();
413 let value = Some(
414 cookie_str
415 .get(eq_pos.saturating_add(1)..)
416 .unwrap_or("")
417 .trim()
418 .to_string(),
419 );
420 cookies.push(HttpCookie {
421 name,
422 value,
423 position,
424 });
425 } else {
426 cookies.push(HttpCookie {
427 name: cookie_str.to_string(),
428 value: None,
429 position,
430 });
431 }
432 position = position.saturating_add(1);
433 }
434
435 cookies
436 }
437
438 fn is_valid_method(&self, method: &str) -> bool {
439 matches!(
440 method,
441 "GET"
442 | "POST"
443 | "PUT"
444 | "DELETE"
445 | "HEAD"
446 | "OPTIONS"
447 | "PATCH"
448 | "TRACE"
449 | "CONNECT"
450 | "PROPFIND"
451 | "PROPPATCH"
452 | "MKCOL"
453 | "COPY"
454 | "MOVE"
455 | "LOCK"
456 | "UNLOCK"
457 | "MKCALENDAR"
458 | "REPORT"
459 )
460 }
461}
462
463impl Default for Http1Parser {
464 fn default() -> Self {
465 Self::new()
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472
473 fn unwrap_parser_result<T>(result: Result<Option<T>, Http1ParseError>) -> T {
474 match result {
475 Ok(Some(value)) => value,
476 Ok(None) => {
477 panic!("Parser returned None when Some was expected")
478 }
479 Err(e) => {
480 panic!("Parser failed with error: {e}")
481 }
482 }
483 }
484
485 fn assert_parser_none<T>(result: Result<Option<T>, Http1ParseError>) {
486 match result {
487 Ok(None) => {}
488 Ok(Some(_)) => panic!("Expected None but got Some"),
489 Err(e) => panic!("Expected None but got error: {e}"),
490 }
491 }
492
493 #[test]
494 fn test_parse_simple_request() {
495 let parser = Http1Parser::new();
496 let data = b"GET /path HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\n\r\n";
497
498 let request = unwrap_parser_result(parser.parse_request(data));
499 assert_eq!(request.method, "GET");
500 assert_eq!(request.uri, "/path");
501 assert_eq!(request.version, http::Version::V11);
502 assert_eq!(request.headers.len(), 2);
503 assert_eq!(request.host, Some("example.com".to_string()));
504 assert_eq!(request.user_agent, Some("test".to_string()));
505 }
506
507 #[test]
508 fn test_parse_request_with_cookies() {
509 let parser = Http1Parser::new();
510 let data =
511 b"GET / HTTP/1.1\r\nHost: example.com\r\nCookie: name1=value1; name2=value2\r\n\r\n";
512
513 let request = unwrap_parser_result(parser.parse_request(data));
514 assert_eq!(request.cookies.len(), 2);
515 assert_eq!(request.cookies[0].name, "name1");
516 assert_eq!(request.cookies[0].value, Some("value1".to_string()));
517 assert_eq!(request.cookies[1].name, "name2");
518 assert_eq!(request.cookies[1].value, Some("value2".to_string()));
519 }
520
521 #[test]
522 fn test_parse_request_with_referer() {
523 let parser = Http1Parser::new();
524 let data = b"GET /page HTTP/1.1\r\nHost: example.com\r\nReferer: https://google.com/search\r\nUser-Agent: test-browser\r\n\r\n";
525
526 let request = unwrap_parser_result(parser.parse_request(data));
527 assert_eq!(request.method, "GET");
528 assert_eq!(request.uri, "/page");
529 assert_eq!(request.host, Some("example.com".to_string()));
530 assert_eq!(
531 request.referer,
532 Some("https://google.com/search".to_string())
533 );
534 assert_eq!(request.user_agent, Some("test-browser".to_string()));
535 }
536
537 #[test]
538 fn test_parse_request_without_referer() {
539 let parser = Http1Parser::new();
540 let data = b"GET /page HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test-browser\r\n\r\n";
541
542 let request = unwrap_parser_result(parser.parse_request(data));
543 assert_eq!(request.method, "GET");
544 assert_eq!(request.uri, "/page");
545 assert_eq!(request.host, Some("example.com".to_string()));
546 assert_eq!(request.referer, None);
547 assert_eq!(request.user_agent, Some("test-browser".to_string()));
548 }
549
550 #[test]
551 fn test_cookie_and_referer_excluded_from_headers_list() {
552 let parser = Http1Parser::new();
553 let data = b"GET /page HTTP/1.1\r\nHost: example.com\r\nCookie: session=abc123\r\nReferer: https://google.com\r\nUser-Agent: test-browser\r\nAccept: text/html\r\n\r\n";
554
555 let request = unwrap_parser_result(parser.parse_request(data));
556
557 assert_eq!(request.cookies.len(), 1);
558 assert_eq!(request.cookies[0].name, "session");
559 assert_eq!(request.cookies[0].value, Some("abc123".to_string()));
560 assert_eq!(request.referer, Some("https://google.com".to_string()));
561
562 let header_names: Vec<String> = request
563 .headers
564 .iter()
565 .map(|h| h.name.to_lowercase())
566 .collect();
567 assert!(
568 !header_names.contains(&"cookie".to_string()),
569 "Cookie header should not be in headers list"
570 );
571 assert!(
572 !header_names.contains(&"referer".to_string()),
573 "Referer header should not be in headers list"
574 );
575
576 assert!(header_names.contains(&"host".to_string()));
577 assert!(header_names.contains(&"user-agent".to_string()));
578 assert!(header_names.contains(&"accept".to_string()));
579
580 assert_eq!(request.headers.len(), 3);
581 }
582
583 #[test]
584 fn test_parse_response() {
585 let parser = Http1Parser::new();
586 let data = b"HTTP/1.1 200 OK\r\nServer: nginx\r\nContent-Type: text/html\r\n\r\n";
587
588 let response = unwrap_parser_result(parser.parse_response(data));
589 assert_eq!(response.version, http::Version::V11);
590 assert_eq!(response.status_code, 200);
591 assert_eq!(response.reason_phrase, "OK");
592 assert_eq!(response.server, Some("nginx".to_string()));
593 assert_eq!(response.content_type, Some("text/html".to_string()));
594 }
595
596 #[test]
597 fn test_incomplete_request() {
598 let parser = Http1Parser::new();
599 let data = b"GET /path HTTP/1.1\r\nHost: example.com";
600
601 assert_parser_none(parser.parse_request(data));
602 }
603
604 #[test]
605 fn test_malformed_request_line() {
606 let parser = Http1Parser::new();
607 let data = b"INVALID REQUEST LINE\r\n\r\n";
608
609 let result = parser.parse_request(data);
610 assert!(result.is_err());
611 }
612
613 #[test]
614 fn test_header_order_preservation() {
615 let parser = Http1Parser::new();
616 let data =
617 b"GET / HTTP/1.1\r\nZ-Header: first\r\nA-Header: second\r\nM-Header: third\r\n\r\n";
618
619 let result = unwrap_parser_result(parser.parse_request(data));
620
621 assert_eq!(result.headers[0].name, "Z-Header");
622 assert_eq!(result.headers[0].position, 0);
623 assert_eq!(result.headers[1].name, "A-Header");
624 assert_eq!(result.headers[1].position, 1);
625 assert_eq!(result.headers[2].name, "M-Header");
626 assert_eq!(result.headers[2].position, 2);
627 }
628
629 #[test]
630 fn test_case_variations_detection() {
631 let parser = Http1Parser::new();
632 let data = b"GET / HTTP/1.1\r\nHost: example.com\r\nHOST: example2.com\r\n\r\n";
633
634 let result = unwrap_parser_result(parser.parse_request(data));
635
636 assert!(result.parsing_metadata.case_variations.contains_key("host"));
637 assert!(result
638 .parsing_metadata
639 .duplicate_headers
640 .contains(&"host".to_string()));
641 }
642
643 #[test]
646 fn test_extremely_long_request_line() {
647 let parser = Http1Parser::new();
648
649 let long_path = "a".repeat(10000);
651 let request_line = format!("GET /{long_path} HTTP/1.1");
652 let data = format!("{request_line}\r\nHost: example.com\r\n\r\n");
653
654 let result = parser.parse_request(data.as_bytes());
655 assert!(result.is_err());
656
657 if let Err(Http1ParseError::InvalidRequestLine(msg)) = result {
658 assert!(msg.contains("too long"));
659 } else {
660 panic!("Expected InvalidRequestLine error");
661 }
662 }
663
664 #[test]
665 fn test_extremely_long_header() {
666 let parser = Http1Parser::new();
667
668 let long_value = "x".repeat(10000);
670 let data = format!("GET / HTTP/1.1\r\nLong-Header: {long_value}\r\n\r\n");
671
672 let result = parser.parse_request(data.as_bytes());
673 assert!(result.is_err());
674
675 if let Err(Http1ParseError::HeaderTooLong(len)) = result {
676 assert!(len > 8192);
677 } else {
678 panic!("Expected HeaderTooLong error");
679 }
680 }
681
682 #[test]
683 fn test_too_many_headers() {
684 let parser = Http1Parser::new();
685
686 let mut data = String::from("GET / HTTP/1.1\r\n");
688 for i in 0..150 {
689 data.push_str(&format!("Header-{i}: value{i}\r\n"));
690 }
691 data.push_str("\r\n");
692
693 let result = parser.parse_request(data.as_bytes());
694 assert!(result.is_err());
695
696 if let Err(Http1ParseError::TooManyHeaders(count)) = result {
697 assert_eq!(count, 150);
698 } else {
699 panic!("Expected TooManyHeaders error");
700 }
701 }
702
703 #[test]
704 fn test_invalid_utf8_handling() {
705 let parser = Http1Parser::new();
706
707 let mut data = Vec::from("GET / HTTP/1.1\r\nHost: ");
709 data.extend_from_slice(&[0xFF, 0xFE, 0xFD]); data.extend_from_slice(b"\r\n\r\n");
711
712 let result = parser.parse_request(&data);
713 assert!(result.is_err());
714
715 if let Err(Http1ParseError::InvalidUtf8) = result {
716 } else {
718 panic!("Expected InvalidUtf8 error");
719 }
720 }
721
722 #[test]
725 fn test_empty_data() {
726 let parser = Http1Parser::new();
727
728 assert_parser_none(parser.parse_request(b""));
729 assert_parser_none(parser.parse_response(b""));
730 }
731
732 #[test]
733 fn test_only_request_line() {
734 let parser = Http1Parser::new();
735
736 let data = b"GET / HTTP/1.1";
738 assert_parser_none(parser.parse_request(data));
739
740 let data = b"GET / HTTP/1.1\r\n";
742 assert_parser_none(parser.parse_request(data));
743 }
744
745 #[test]
746 fn test_different_line_endings() {
747 let parser = Http1Parser::new();
748
749 let data_lf = b"GET / HTTP/1.1\nHost: example.com\n\n";
751 let result_lf = unwrap_parser_result(parser.parse_request(data_lf));
752 assert_eq!(result_lf.method, "GET");
753
754 let data_crlf = b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
756 let result_crlf = unwrap_parser_result(parser.parse_request(data_crlf));
757 assert_eq!(result_crlf.method, "GET");
758 }
759
760 #[test]
761 fn test_malformed_headers() {
762 let parser = Http1Parser::new();
763
764 let data = b"GET / HTTP/1.1\r\nMalformed Header Without Colon\r\nHost: example.com\r\n\r\n";
766 let result = unwrap_parser_result(parser.parse_request(data));
767 assert!(result.parsing_metadata.has_malformed_headers);
768
769 let data = b"GET / HTTP/1.1\r\n: empty-name\r\nHost: example.com\r\n\r\n";
771 let result = unwrap_parser_result(parser.parse_request(data));
772 assert!(result.parsing_metadata.has_malformed_headers);
773 }
774
775 #[test]
776 fn test_strict_parsing_mode() {
777 let config = Http1Config {
778 strict_parsing: true,
779 ..Default::default()
780 };
781 let parser = Http1Parser { config };
782
783 let data = b"GET / HTTP/1.1\r\nMalformed Header Without Colon\r\n\r\n";
785 let result = parser.parse_request(data);
786 assert!(result.is_err());
787
788 if let Err(Http1ParseError::MalformedHeader(header)) = result {
789 assert_eq!(header, "Malformed Header Without Colon");
790 } else {
791 panic!("Expected MalformedHeader error");
792 }
793 }
794
795 #[test]
796 fn test_invalid_methods() {
797 let parser = Http1Parser::new();
798
799 let invalid_methods = ["INVALID", "123", "", "G E T", "get"];
800
801 for method in invalid_methods {
802 let data = format!("{method} / HTTP/1.1\r\nHost: example.com\r\n\r\n");
803 let result = parser.parse_request(data.as_bytes());
804 assert!(result.is_err(), "Method '{method}' should be invalid");
805 }
806 }
807
808 #[test]
809 fn test_valid_extended_methods() {
810 let parser = Http1Parser::new();
811
812 let valid_methods = [
813 "PROPFIND",
814 "PROPPATCH",
815 "MKCOL",
816 "COPY",
817 "MOVE",
818 "LOCK",
819 "UNLOCK",
820 ];
821
822 for method in valid_methods {
823 let data = format!("{method} / HTTP/1.1\r\nHost: example.com\r\n\r\n");
824 let result = unwrap_parser_result(parser.parse_request(data.as_bytes()));
825 assert_eq!(result.method, method);
826 }
827 }
828
829 #[test]
830 fn test_invalid_http_versions() {
831 let parser = Http1Parser::new();
832
833 let invalid_versions = ["HTTP/2.0", "HTTP/0.9", "HTTP/1.2", "HTTP/1", "HTTP", "1.1"];
834
835 for version in invalid_versions {
836 let data = format!("GET / {version}\r\nHost: example.com\r\n\r\n");
837 let result = parser.parse_request(data.as_bytes());
838 assert!(result.is_err(), "Version '{version}' should be invalid");
839 }
840 }
841
842 #[test]
843 fn test_invalid_status_codes() {
844 let parser = Http1Parser::new();
845
846 let invalid_codes = ["abc", "999999", "", "-1", "1.5"];
847
848 for code in invalid_codes {
849 let data = format!("HTTP/1.1 {code} OK\r\nServer: test\r\n\r\n");
850 let result = parser.parse_response(data.as_bytes());
851 assert!(result.is_err(), "Status code '{code}' should be invalid");
852 }
853 }
854
855 #[test]
856 fn test_edge_case_status_lines() {
857 let parser = Http1Parser::new();
858
859 let data = b"HTTP/1.1 404\r\nServer: test\r\n\r\n";
861 let result = unwrap_parser_result(parser.parse_response(data));
862 assert_eq!(result.status_code, 404);
863 assert_eq!(result.reason_phrase, "");
864
865 let data = b"HTTP/1.1 404 Not Found Here\r\nServer: test\r\n\r\n";
867 let result = unwrap_parser_result(parser.parse_response(data));
868 assert_eq!(result.status_code, 404);
869 assert_eq!(result.reason_phrase, "Not Found Here");
870 }
871
872 #[test]
873 fn test_cookie_parsing_edge_cases() {
874 let parser = Http1Parser::new();
875
876 let cookie_test_cases = vec![
877 ("", 0), ("name=value", 1), ("name=", 1), ("name", 1), ("name=value; other=test", 2), (" name = value ; other=test", 2), ("name=value;", 1), (";name=value", 1), ("name=value;;other=test", 2), ("name=value; ; other=test", 2), ];
888
889 for (cookie_str, expected_count) in cookie_test_cases {
890 let data =
891 format!("GET / HTTP/1.1\r\nHost: example.com\r\nCookie: {cookie_str}\r\n\r\n");
892 let result = unwrap_parser_result(parser.parse_request(data.as_bytes()));
893 assert_eq!(
894 result.cookies.len(),
895 expected_count,
896 "Failed for cookie: '{cookie_str}'"
897 );
898 }
899 }
900
901 #[test]
902 fn test_parse_cookies_direct() {
903 let parser = Http1Parser::new();
904
905 let test_cases = vec![
906 ("", 0),
907 ("name=value", 1),
908 ("name=", 1),
909 ("name", 1),
910 ("name=value; other=test", 2),
911 (" name = value ", 1),
912 ("name=value;", 1),
913 (";name=value", 1),
914 ("name=value;;other=test", 2),
915 ];
916
917 for (cookie_str, expected_count) in test_cases {
918 let cookies = parser.parse_cookies(cookie_str);
919 assert_eq!(
920 cookies.len(),
921 expected_count,
922 "Failed for case: '{cookie_str}'"
923 );
924
925 match cookie_str {
926 "" => {
927 assert!(cookies.is_empty());
928 }
929 "name=value" => {
930 assert_eq!(cookies[0].name, "name");
931 assert_eq!(cookies[0].value, Some("value".to_string()));
932 assert_eq!(cookies[0].position, 0);
933 }
934 "name=" => {
935 assert_eq!(cookies[0].name, "name");
936 assert_eq!(cookies[0].value, Some("".to_string()));
937 assert_eq!(cookies[0].position, 0);
938 }
939 "name" => {
940 assert_eq!(cookies[0].name, "name");
941 assert_eq!(cookies[0].value, None);
942 assert_eq!(cookies[0].position, 0);
943 }
944 "name=value; other=test" => {
945 assert_eq!(cookies[0].name, "name");
946 assert_eq!(cookies[0].value, Some("value".to_string()));
947 assert_eq!(cookies[0].position, 0);
948 assert_eq!(cookies[1].name, "other");
949 assert_eq!(cookies[1].value, Some("test".to_string()));
950 assert_eq!(cookies[1].position, 1);
951 }
952 " name = value " => {
953 assert_eq!(cookies[0].name, "name");
954 assert_eq!(cookies[0].value, Some("value".to_string()));
955 assert_eq!(cookies[0].position, 0);
956 }
957 "name=value;" => {
958 assert_eq!(cookies[0].name, "name");
959 assert_eq!(cookies[0].value, Some("value".to_string()));
960 assert_eq!(cookies[0].position, 0);
961 }
962 ";name=value" => {
963 assert_eq!(cookies[0].name, "name");
964 assert_eq!(cookies[0].value, Some("value".to_string()));
965 assert_eq!(cookies[0].position, 0);
966 }
967 "name=value;;other=test" => {
968 assert_eq!(cookies[0].name, "name");
969 assert_eq!(cookies[0].value, Some("value".to_string()));
970 assert_eq!(cookies[0].position, 0);
971 assert_eq!(cookies[1].name, "other");
972 assert_eq!(cookies[1].value, Some("test".to_string()));
973 assert_eq!(cookies[1].position, 1);
974 }
975 _ => {}
976 }
977 }
978 }
979
980 #[test]
981 fn test_parse_cookies_rfc6265_compliance() {
982 let parser = Http1Parser::new();
983
984 let rfc_cases = vec![
986 (
987 "session_id=abc123; user_id=456; theme=dark; lang=en",
988 vec![
989 ("session_id", Some("abc123")),
990 ("user_id", Some("456")),
991 ("theme", Some("dark")),
992 ("lang", Some("en")),
993 ],
994 ),
995 (
996 "token=xyz; secure; httponly",
997 vec![("token", Some("xyz")), ("secure", None), ("httponly", None)],
998 ),
999 ];
1000
1001 for (cookie_str, expected_cookies) in rfc_cases {
1002 let cookies = parser.parse_cookies(cookie_str);
1003 assert_eq!(
1004 cookies.len(),
1005 expected_cookies.len(),
1006 "Failed for RFC case: '{cookie_str}'"
1007 );
1008
1009 for (i, (expected_name, expected_value)) in expected_cookies.iter().enumerate() {
1010 assert_eq!(cookies[i].name, *expected_name);
1011 assert_eq!(cookies[i].value, expected_value.map(|v| v.to_string()));
1012 assert_eq!(cookies[i].position, i);
1013 }
1014 }
1015 }
1016
1017 #[test]
1018 fn test_header_value_edge_cases() {
1019 let parser = Http1Parser::new();
1020
1021 let data = b"GET / HTTP/1.1\r\nEmpty-Header:\r\nHost: example.com\r\n\r\n";
1023 let result = unwrap_parser_result(parser.parse_request(data));
1024 let empty_header = result.headers.iter().find(|h| h.name == "Empty-Header");
1025 assert!(empty_header.is_some(), "Empty-Header should be present");
1026 assert_eq!(
1027 empty_header.as_ref().and_then(|h| h.value.as_deref()),
1028 Some("")
1029 );
1030
1031 let data = b"GET / HTTP/1.1\r\nSpaces-Header: \r\nHost: example.com\r\n\r\n";
1033 let result = unwrap_parser_result(parser.parse_request(data));
1034 let spaces_header = result.headers.iter().find(|h| h.name == "Spaces-Header");
1035 assert!(spaces_header.is_some(), "Spaces-Header should be present");
1036 assert_eq!(
1037 spaces_header.as_ref().and_then(|h| h.value.as_deref()),
1038 Some("")
1039 );
1040
1041 let data =
1043 b"GET / HTTP/1.1\r\nTrim-Header: value with spaces \r\nHost: example.com\r\n\r\n";
1044 let result = unwrap_parser_result(parser.parse_request(data));
1045 let trim_header = result.headers.iter().find(|h| h.name == "Trim-Header");
1046 assert!(trim_header.is_some(), "Trim-Header should be present");
1047 assert_eq!(
1048 trim_header.as_ref().and_then(|h| h.value.as_deref()),
1049 Some("value with spaces")
1050 );
1051 }
1052
1053 #[test]
1054 fn test_request_line_edge_cases() {
1055 let parser = Http1Parser::new();
1056
1057 let data = b"GET HTTP/1.1\r\nHost: example.com\r\n\r\n";
1059 let result = parser.parse_request(data);
1060 assert!(result.is_err());
1061
1062 let data = b"GET / HTTP/1.1 extra\r\nHost: example.com\r\n\r\n";
1064 let result = parser.parse_request(data);
1065 assert!(result.is_err());
1066
1067 let data = b" / HTTP/1.1\r\nHost: example.com\r\n\r\n";
1069 let result = parser.parse_request(data);
1070 assert!(result.is_err());
1071 }
1072
1073 #[test]
1074 fn test_content_length_parsing() {
1075 let parser = Http1Parser::new();
1076
1077 let data = b"GET / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 42\r\n\r\n";
1079 let result = unwrap_parser_result(parser.parse_request(data));
1080 assert_eq!(result.content_length, Some(42));
1081
1082 let data = b"GET / HTTP/1.1\r\nHost: example.com\r\nContent-Length: abc\r\n\r\n";
1084 let result = unwrap_parser_result(parser.parse_request(data));
1085 assert_eq!(result.content_length, None);
1086
1087 let data = b"GET / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 42\r\nContent-Length: 24\r\n\r\n";
1089 let result = unwrap_parser_result(parser.parse_request(data));
1090 assert_eq!(result.content_length, Some(42));
1091 }
1092
1093 #[test]
1094 fn test_can_parse_detection() {
1095 use crate::http_process::HttpProcessors;
1096 let processors = HttpProcessors::new();
1097
1098 assert!(processors
1100 .parse_request(b"GET / HTTP/1.1\r\n\r\n")
1101 .is_some());
1102 assert!(processors
1103 .parse_request(b"POST /api HTTP/1.0\r\n\r\n")
1104 .is_some());
1105 assert!(processors
1106 .parse_request(b"PUT /data HTTP/1.1\r\n\r\n")
1107 .is_some());
1108
1109 assert!(processors
1111 .parse_response(b"HTTP/1.1 200 OK\r\n\r\n")
1112 .is_some());
1113 assert!(processors
1114 .parse_response(b"HTTP/1.0 404 Not Found\r\n\r\n")
1115 .is_some());
1116
1117 assert!(processors.parse_request(b"").is_none());
1119 assert!(processors.parse_request(b"short").is_none());
1120 assert!(processors.parse_request(b"INVALID DATA HERE").is_none());
1121 assert!(processors.parse_request(b"PRI * HTTP/2.0\r\n").is_none()); }
1123
1124 #[test]
1125 fn test_error_display_formatting() {
1126 let errors = vec![
1128 Http1ParseError::InvalidRequestLine("test".to_string()),
1129 Http1ParseError::InvalidStatusLine("test".to_string()),
1130 Http1ParseError::InvalidVersion("test".to_string()),
1131 Http1ParseError::InvalidMethod("test".to_string()),
1132 Http1ParseError::InvalidStatusCode("test".to_string()),
1133 Http1ParseError::HeaderTooLong(12345),
1134 Http1ParseError::TooManyHeaders(999),
1135 Http1ParseError::MalformedHeader("test".to_string()),
1136 Http1ParseError::IncompleteData,
1137 Http1ParseError::InvalidUtf8,
1138 ];
1139
1140 for error in errors {
1141 let formatted = format!("{error}");
1142 assert!(!formatted.is_empty());
1143 assert!(!formatted.contains("Debug")); }
1145 }
1146
1147 #[test]
1148 fn test_config_limits() {
1149 let config = Http1Config {
1151 max_headers: 2,
1152 max_request_line_length: 50,
1153 max_header_length: 30,
1154 preserve_header_order: true,
1155 parse_cookies: false,
1156 strict_parsing: true,
1157 };
1158 let parser = Http1Parser { config };
1159
1160 let data = b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
1162 let result = unwrap_parser_result(parser.parse_request(data));
1163 assert_eq!(result.method, "GET");
1164 assert!(result.cookies.is_empty()); let data =
1168 b"GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\nAccept: */*\r\n\r\n";
1169 let result = parser.parse_request(data);
1170 assert!(result.is_err());
1171 }
1172
1173 #[test]
1174 fn test_performance_metadata() {
1175 let parser = Http1Parser::new();
1176 let data = b"GET /path HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\n\r\n";
1177
1178 let result = unwrap_parser_result(parser.parse_request(data));
1179
1180 assert!(result.parsing_metadata.parsing_time_ns > 0);
1182 assert_eq!(result.parsing_metadata.header_count, 2);
1183 assert_eq!(
1184 result.parsing_metadata.request_line_length,
1185 "GET /path HTTP/1.1".len()
1186 );
1187 assert!(result.parsing_metadata.total_headers_length > 0);
1188 assert!(!result.parsing_metadata.has_malformed_headers);
1189 }
1190}