Skip to main content

eggress_protocol_http/
detect.rs

1use eggress_core::detect::{DetectResult, ProtocolDetector};
2use eggress_core::ProtocolId;
3
4/// HTTP protocol detector.
5///
6/// Checks if the input starts with a known HTTP method or "HTTP/".
7pub struct HttpDetector;
8
9const HTTP_METHODS: &[&[u8]] = &[
10    b"GET ",
11    b"POST ",
12    b"PUT ",
13    b"DELETE ",
14    b"HEAD ",
15    b"OPTIONS ",
16    b"PATCH ",
17    b"CONNECT ",
18    b"TRACE ",
19];
20
21impl ProtocolDetector for HttpDetector {
22    fn id(&self) -> ProtocolId {
23        ProtocolId::Http
24    }
25
26    fn detect(&self, prefix: &[u8]) -> DetectResult {
27        if prefix.is_empty() {
28            return DetectResult::NeedMore { minimum: 1 };
29        }
30
31        // Check for "HTTP/" (response prefix)
32        if prefix.starts_with(b"HTTP/") {
33            if prefix.len() >= 5 {
34                return DetectResult::Match { confidence: 95 };
35            }
36            return DetectResult::NeedMore { minimum: 5 };
37        }
38
39        // Partial "HTTP/" prefix must not be treated as no-match.
40        if b"HTTP/".starts_with(prefix) {
41            return DetectResult::NeedMore { minimum: 5 };
42        }
43
44        // Check for known HTTP methods
45        for method in HTTP_METHODS {
46            if prefix.starts_with(method) {
47                return DetectResult::Match { confidence: 100 };
48            }
49            // Check if prefix is a partial match for this method
50            if method.starts_with(prefix) {
51                return DetectResult::NeedMore {
52                    minimum: method.len(),
53                };
54            }
55        }
56
57        DetectResult::NoMatch
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_http_get_match() {
67        let detector = HttpDetector;
68        assert_eq!(
69            detector.detect(b"GET / HTTP/1.1\r\n"),
70            DetectResult::Match { confidence: 100 }
71        );
72    }
73
74    #[test]
75    fn test_http_connect_match() {
76        let detector = HttpDetector;
77        assert_eq!(
78            detector.detect(b"CONNECT example.com:443 HTTP/1.1\r\n"),
79            DetectResult::Match { confidence: 100 }
80        );
81    }
82
83    #[test]
84    fn test_http_response_match() {
85        let detector = HttpDetector;
86        assert_eq!(
87            detector.detect(b"HTTP/1.1 200 OK\r\n"),
88            DetectResult::Match { confidence: 95 }
89        );
90    }
91
92    #[test]
93    fn test_http_need_more() {
94        let detector = HttpDetector;
95        assert_eq!(
96            detector.detect(b"GE"),
97            DetectResult::NeedMore { minimum: 4 }
98        );
99    }
100
101    #[test]
102    fn test_http_empty() {
103        let detector = HttpDetector;
104        assert_eq!(detector.detect(b""), DetectResult::NeedMore { minimum: 1 });
105    }
106
107    #[test]
108    fn test_http_no_match() {
109        let detector = HttpDetector;
110        assert_eq!(detector.detect(b"\x05"), DetectResult::NoMatch);
111        assert_eq!(detector.detect(b"\x04"), DetectResult::NoMatch);
112    }
113
114    #[test]
115    fn test_http_post_match() {
116        let detector = HttpDetector;
117        assert_eq!(
118            detector.detect(b"POST /api HTTP/1.1\r\n"),
119            DetectResult::Match { confidence: 100 }
120        );
121    }
122
123    #[test]
124    fn test_http_partial_method() {
125        let detector = HttpDetector;
126        // "P" could be POST, PUT, PATCH
127        assert!(matches!(
128            detector.detect(b"P"),
129            DetectResult::NeedMore { .. }
130        ));
131    }
132
133    #[test]
134    fn test_http_partial_response_prefix_needs_more() {
135        let detector = HttpDetector;
136        // Single byte that could be the start of "HTTP/" must not be rejected
137        // as NoMatch (which would happen on slow server-side dribbles).
138        assert!(matches!(
139            detector.detect(b"H"),
140            DetectResult::NeedMore { minimum: 5 }
141        ));
142        assert!(matches!(
143            detector.detect(b"HT"),
144            DetectResult::NeedMore { minimum: 5 }
145        ));
146        assert!(matches!(
147            detector.detect(b"HTT"),
148            DetectResult::NeedMore { minimum: 5 }
149        ));
150        assert!(matches!(
151            detector.detect(b"HTTP"),
152            DetectResult::NeedMore { minimum: 5 }
153        ));
154    }
155}