Skip to main content

huginn_net_http/http/
mod.rs

1pub mod common;
2pub mod languages;
3pub mod observable;
4
5pub use common::{
6    get_diagnostic, HeaderSource, HttpCookie, HttpHeader, HttpParser, HttpProcessor,
7    ParsingMetadata,
8};
9pub use languages::get_highest_quality_language;
10pub use observable::*;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum Version {
14    /// HTTP/1.0
15    V10,
16    /// HTTP/1.1
17    V11,
18    /// HTTP/2
19    V20,
20    /// HTTP/3
21    V30,
22    /// Matches any HTTP version (used in database signatures).
23    Any,
24}
25
26impl Version {
27    pub fn parse(version_str: &str) -> Option<Self> {
28        match version_str {
29            "HTTP/1.0" => Some(Version::V10),
30            "HTTP/1.1" => Some(Version::V11),
31            "HTTP/2" | "HTTP/2.0" => Some(Version::V20),
32            "HTTP/3" | "HTTP/3.0" => Some(Version::V30),
33            _ => None,
34        }
35    }
36
37    pub fn as_str(&self) -> &'static str {
38        match self {
39            Version::V10 => "HTTP/1.0",
40            Version::V11 => "HTTP/1.1",
41            Version::V20 => "HTTP/2",
42            Version::V30 => "HTTP/3",
43            Version::Any => "Any",
44        }
45    }
46}
47
48impl std::str::FromStr for Version {
49    type Err = ();
50
51    fn from_str(s: &str) -> Result<Self, Self::Err> {
52        Self::parse(s).ok_or(())
53    }
54}
55
56impl core::fmt::Display for Version {
57    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58        f.write_str(match self {
59            Version::V10 => "0",
60            Version::V11 => "1",
61            Version::V20 => "2",
62            Version::V30 => "3",
63            Version::Any => "*",
64        })
65    }
66}
67
68/// A header name (and optional value) used in p0f-style HTTP signatures.
69///
70/// Headers may be marked optional (`?`) to signal "match if present, ignore
71/// if absent".
72#[derive(Clone, Debug, PartialEq)]
73pub struct Header {
74    pub optional: bool,
75    pub name: String,
76    pub value: Option<String>,
77}
78
79impl Header {
80    pub fn new<S: AsRef<str>>(name: S) -> Self {
81        Header { optional: false, name: name.as_ref().to_owned(), value: None }
82    }
83
84    pub fn with_value<S: AsRef<str>>(mut self, value: S) -> Self {
85        self.value = Some(value.as_ref().to_owned());
86        self
87    }
88
89    pub fn with_optional_value<S: AsRef<str>>(mut self, value: Option<S>) -> Self {
90        self.value = value.map(|v| v.as_ref().to_owned());
91        self
92    }
93
94    pub fn optional(mut self) -> Self {
95        self.optional = true;
96        self
97    }
98}
99
100impl core::fmt::Display for Header {
101    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102        if self.optional {
103            f.write_str("?")?;
104        }
105        f.write_str(&self.name)?;
106        if let Some(ref value) = self.value {
107            write!(f, "=[{value}]")?;
108        }
109        Ok(())
110    }
111}
112
113/// HTTP diagnostic flags derived from comparing User-Agent claims with the
114/// matched OS signature.
115#[derive(Clone, Debug, PartialEq)]
116pub enum HttpDiagnosis {
117    Dishonest,
118    Anonymous,
119    Generic,
120    None,
121}
122
123impl core::fmt::Display for HttpDiagnosis {
124    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
125        f.write_str(match self {
126            HttpDiagnosis::Dishonest => "dishonest",
127            HttpDiagnosis::Anonymous => "anonymous",
128            HttpDiagnosis::Generic => "generic",
129            HttpDiagnosis::None => "none",
130        })
131    }
132}
133
134pub fn request_optional_headers() -> Vec<&'static str> {
135    vec![
136        "Cookie",
137        "Referer",
138        "Origin",
139        "Range",
140        "If-Modified-Since",
141        "If-None-Match",
142        "Via",
143        "X-Forwarded-For",
144        "Authorization",
145        "Proxy-Authorization",
146        "Cache-Control",
147    ]
148}
149
150pub fn response_optional_headers() -> Vec<&'static str> {
151    vec![
152        "Set-Cookie",
153        "Last-Modified",
154        "ETag",
155        "Content-Length",
156        "Content-Disposition",
157        "Cache-Control",
158        "Expires",
159        "Pragma",
160        "Location",
161        "Refresh",
162        "Content-Range",
163        "Vary",
164    ]
165}
166
167pub fn request_skip_value_headers() -> Vec<&'static str> {
168    vec!["Host", "User-Agent"]
169}
170
171pub fn response_skip_value_headers() -> Vec<&'static str> {
172    vec!["Date", "Content-Type", "Server"]
173}
174
175pub fn request_common_headers() -> Vec<&'static str> {
176    vec![
177        "Host",
178        "User-Agent",
179        "Connection",
180        "Accept",
181        "Accept-Encoding",
182        "Accept-Language",
183        "Accept-Charset",
184        "Keep-Alive",
185    ]
186}
187
188pub fn response_common_headers() -> Vec<&'static str> {
189    vec!["Content-Type", "Connection", "Keep-Alive", "Accept-Ranges", "Date"]
190}