huginn_net_db/
http.rs

1#[derive(Clone, Debug, PartialEq)]
2pub struct Signature {
3    /// HTTP version
4    pub version: Version,
5    /// ordered list of headers that should appear in matching traffic.
6    pub horder: Vec<Header>,
7    /// list of headers that must *not* appear in matching traffic.
8    pub habsent: Vec<Header>,
9    /// expected substring in 'User-Agent' or 'Server'.
10    pub expsw: String,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum HttpMatchQuality {
15    High,
16    Medium,
17    Low,
18    Bad,
19}
20
21impl HttpMatchQuality {
22    pub fn as_score(self) -> u32 {
23        match self {
24            HttpMatchQuality::High => 0,
25            HttpMatchQuality::Medium => 1,
26            HttpMatchQuality::Low => 2,
27            HttpMatchQuality::Bad => 3,
28        }
29    }
30}
31
32impl crate::db_matching_trait::MatchQuality for HttpMatchQuality {
33    // HTTP has 4 components, each can contribute max 3 points (Bad)
34    const MAX_DISTANCE: u32 = 12;
35
36    fn distance_to_score(distance: u32) -> f32 {
37        match distance {
38            0 => 1.0,
39            1 => 0.95,
40            2 => 0.90,
41            3 => 0.80,
42            4..=5 => 0.70,
43            6..=7 => 0.60,
44            8..=9 => 0.40,
45            10..=11 => 0.20,
46            d if d <= Self::MAX_DISTANCE => 0.10,
47            _ => 0.05,
48        }
49    }
50}
51
52/// Version of the HTTP protocol used in a request or response.
53/// Used in signatures to distinguish behavior between HTTP/1.0 and HTTP/1.1.
54/// The `Any` variant is used in database signatures to match any HTTP version.
55#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
56pub enum Version {
57    /// HTTP/1.0
58    V10,
59    /// HTTP/1.1
60    V11,
61    /// HTTP/2
62    V20,
63    /// HTTP/3
64    V30,
65    /// Matches any HTTP version (used in database signatures).
66    Any,
67}
68
69impl Version {
70    pub fn parse(version_str: &str) -> Option<Self> {
71        match version_str {
72            "HTTP/1.0" => Some(Version::V10),
73            "HTTP/1.1" => Some(Version::V11),
74            "HTTP/2" | "HTTP/2.0" => Some(Version::V20),
75            "HTTP/3" | "HTTP/3.0" => Some(Version::V30),
76            _ => None,
77        }
78    }
79}
80
81impl std::str::FromStr for Version {
82    type Err = ();
83
84    fn from_str(s: &str) -> Result<Self, Self::Err> {
85        Self::parse(s).ok_or(())
86    }
87}
88
89impl Version {
90    pub fn as_str(&self) -> &'static str {
91        match self {
92            Version::V10 => "HTTP/1.0",
93            Version::V11 => "HTTP/1.1",
94            Version::V20 => "HTTP/2",
95            Version::V30 => "HTTP/3",
96            Version::Any => "Any",
97        }
98    }
99}
100
101#[derive(Clone, Debug, PartialEq)]
102pub struct Header {
103    pub optional: bool,
104    pub name: String,
105    pub value: Option<String>,
106}
107
108#[derive(Clone, Debug, PartialEq)]
109pub enum HttpDiagnosis {
110    Dishonest,
111    Anonymous,
112    Generic,
113    None,
114}
115
116#[cfg(test)]
117pub fn header<S: AsRef<str>>(name: S) -> Header {
118    Header::new(name)
119}
120
121impl Header {
122    pub fn new<S: AsRef<str>>(name: S) -> Self {
123        Header {
124            optional: false,
125            name: name.as_ref().to_owned(),
126            value: None,
127        }
128    }
129
130    pub fn with_value<S: AsRef<str>>(mut self, value: S) -> Self {
131        self.value = Some(value.as_ref().to_owned());
132        self
133    }
134
135    pub fn with_optional_value<S: AsRef<str>>(mut self, value: Option<S>) -> Self {
136        self.value = value.map(|v| v.as_ref().to_owned());
137        self
138    }
139
140    pub fn optional(mut self) -> Self {
141        self.optional = true;
142        self
143    }
144}
145
146pub fn request_optional_headers() -> Vec<&'static str> {
147    vec![
148        "Cookie",
149        "Referer",
150        "Origin",
151        "Range",
152        "If-Modified-Since",
153        "If-None-Match",
154        "Via",
155        "X-Forwarded-For",
156        "Authorization",
157        "Proxy-Authorization",
158        "Cache-Control",
159    ]
160}
161
162pub fn response_optional_headers() -> Vec<&'static str> {
163    vec![
164        "Set-Cookie",
165        "Last-Modified",
166        "ETag",
167        "Content-Length",
168        "Content-Disposition",
169        "Cache-Control",
170        "Expires",
171        "Pragma",
172        "Location",
173        "Refresh",
174        "Content-Range",
175        "Vary",
176    ]
177}
178
179pub fn request_skip_value_headers() -> Vec<&'static str> {
180    vec!["Host", "User-Agent"]
181}
182
183pub fn response_skip_value_headers() -> Vec<&'static str> {
184    vec!["Date", "Content-Type", "Server"]
185}
186
187pub fn request_common_headers() -> Vec<&'static str> {
188    vec![
189        "Host",
190        "User-Agent",
191        "Connection",
192        "Accept",
193        "Accept-Encoding",
194        "Accept-Language",
195        "Accept-Charset",
196        "Keep-Alive",
197    ]
198}
199
200pub fn response_common_headers() -> Vec<&'static str> {
201    vec![
202        "Content-Type",
203        "Connection",
204        "Keep-Alive",
205        "Accept-Ranges",
206        "Date",
207    ]
208}