huginn_net_http/
http1_parser.rs

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
93/// HTTP/1.x Protocol Parser
94///
95/// Provides parsing capabilities for HTTP/1.0 and HTTP/1.1 requests and responses according to RFC 7230.
96/// Supports header parsing, cookie extraction, and various configuration options for security and performance.
97///
98/// # Thread Safety
99///
100/// **This parser is thread-safe.** Unlike the HTTP/2 parser, this parser does not maintain internal state
101/// and can be safely shared between threads or used concurrently.
102pub 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        // HTTP/1.x parser should only accept HTTP/1.0 and HTTP/1.1
286        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        // HTTP/1.x parser should only accept HTTP/1.0 and HTTP/1.1
310        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    /// HTTP/1.x cookie parsing - single cookie header with '; ' separation according to RFC 6265
401    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    // ========== SECURITY TESTS ==========
644
645    #[test]
646    fn test_extremely_long_request_line() {
647        let parser = Http1Parser::new();
648
649        // Create request line longer than max_request_line_length (8192)
650        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        // Create header longer than max_header_length (8192)
669        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        // Create more than max_headers (100)
687        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        // Create data with invalid UTF-8 sequences
708        let mut data = Vec::from("GET / HTTP/1.1\r\nHost: ");
709        data.extend_from_slice(&[0xFF, 0xFE, 0xFD]); // Invalid UTF-8
710        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            // Expected
717        } else {
718            panic!("Expected InvalidUtf8 error");
719        }
720    }
721
722    // ========== EDGE CASES ==========
723
724    #[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        // No headers, no empty line
737        let data = b"GET / HTTP/1.1";
738        assert_parser_none(parser.parse_request(data));
739
740        // With CRLF but no empty line
741        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        // Test with LF only (Unix style)
750        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        // Test with CRLF (Windows/HTTP standard)
755        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        // Header without colon (non-strict mode)
765        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        // Header with empty name
770        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        // Malformed header should fail in strict mode
784        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        // Status line without reason phrase
860        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        // Status line with spaces in reason phrase
866        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),                               // Empty cookie header
878            ("name=value", 1),                     // Simple cookie
879            ("name=", 1),                          // Empty value
880            ("name", 1),                           // No value
881            ("name=value; other=test", 2),         // Multiple cookies
882            ("  name  =  value  ; other=test", 2), // Whitespace handling
883            ("name=value;", 1),                    // Trailing semicolon
884            (";name=value", 1),                    // Leading semicolon
885            ("name=value;;other=test", 2),         // Double semicolon
886            ("name=value; ; other=test", 2),       // Empty cookie between
887        ];
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        // RFC 6265 examples - HTTP/1.x single cookie header format
985        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        // Header with no value
1022        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        // Header with only spaces as value
1032        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        // Header with leading/trailing spaces
1042        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        // Too few parts
1058        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        // Too many parts (extra spaces)
1063        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        // Empty method
1068        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        // Valid content length
1078        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        // Invalid content length (non-numeric)
1083        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        // Multiple content length headers (should use first valid one)
1088        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        // Valid HTTP/1.x requests - should be parseable
1099        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        // Valid HTTP/1.x responses - should be parseable
1110        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        // Invalid data - should not be parseable
1118        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()); // HTTP/2 preface
1122    }
1123
1124    #[test]
1125    fn test_error_display_formatting() {
1126        // Test that all error types format correctly
1127        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")); // Should be Display, not Debug
1144        }
1145    }
1146
1147    #[test]
1148    fn test_config_limits() {
1149        // Test with very restrictive config
1150        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        // Should work within limits
1161        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()); // Cookie parsing disabled
1165
1166        // Should fail when exceeding header count limit
1167        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        // Verify metadata is populated
1181        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}