huginn_net_http/
output.rs

1use huginn_net_db::{Label, MatchQualityType, Type};
2use std::fmt;
3use std::fmt::Formatter;
4
5/// Represents the output from HTTP analysis.
6///
7/// This struct contains various optional outputs that can be derived
8/// from analyzing HTTP packets.
9#[derive(Debug)]
10pub struct HttpAnalysisResult {
11    /// Information derived from HTTP request packets.
12    pub http_request: Option<HttpRequestOutput>,
13
14    /// Information derived from HTTP response packets.
15    pub http_response: Option<HttpResponseOutput>,
16}
17
18#[derive(Debug, Clone, PartialEq)]
19pub struct IpPort {
20    pub ip: std::net::IpAddr,
21    pub port: u16,
22}
23
24impl IpPort {
25    pub fn new(ip: std::net::IpAddr, port: u16) -> Self {
26        Self { ip, port }
27    }
28}
29use crate::observable::{ObservableHttpRequest, ObservableHttpResponse};
30use huginn_net_db::http::HttpDiagnosis;
31
32#[derive(Debug)]
33pub struct BrowserQualityMatched {
34    pub browser: Option<Browser>,
35    pub quality: MatchQualityType,
36}
37
38/// Represents a browser.
39///
40/// This struct contains the name, family, variant, and kind of browser.
41/// Examples:
42/// - name: "", family: "chrome", variant: "11.x to 26.x", kind: Type::Specified
43/// - name: "", family: "firefox", variant: "3.x", kind: Type::Specified
44#[derive(Debug)]
45pub struct Browser {
46    pub name: String,
47    pub family: Option<String>,
48    pub variant: Option<String>,
49    pub kind: Type,
50}
51
52impl From<&Label> for Browser {
53    fn from(label: &Label) -> Self {
54        Browser {
55            name: label.name.clone(),
56            family: label.class.clone(),
57            variant: label.flavor.clone(),
58            kind: label.ty.clone(),
59        }
60    }
61}
62
63/// Holds information derived from analyzing HTTP request headers.
64///
65/// This structure contains details about the client, the detected application
66/// (if any), the preferred language, diagnostic parameters related to HTTP behavior,
67/// and the raw HTTP signature.
68#[derive(Debug)]
69pub struct HttpRequestOutput {
70    /// The source IP address and port of the client making the request.
71    pub source: IpPort,
72    /// The destination IP address and port of the server receiving the request.
73    pub destination: IpPort,
74    /// The preferred language indicated in the `Accept-Language` header, if present.
75    pub lang: Option<String>,
76    /// Diagnostic information about potential HTTP specification violations or common practices.
77    pub diagnosis: HttpDiagnosis,
78    /// The browser with the highest quality that matches the HTTP request.
79    pub browser_matched: BrowserQualityMatched,
80    /// The raw signature representing the HTTP headers and their order.
81    pub sig: ObservableHttpRequest,
82}
83
84impl fmt::Display for HttpRequestOutput {
85    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
86        write!(
87            f,
88            "[HTTP Request] {}:{} → {}:{}\n\
89              Browser: {}\n\
90              Lang:    {}\n\
91              Params:  {}\n\
92              Sig:     {}\n",
93            self.source.ip,
94            self.source.port,
95            self.destination.ip,
96            self.destination.port,
97            self.browser_matched
98                .browser
99                .as_ref()
100                .map_or("???".to_string(), |browser| {
101                    format!(
102                        "{}:{}",
103                        browser.family.as_deref().unwrap_or("???"),
104                        browser.variant.as_deref().unwrap_or("???")
105                    )
106                }),
107            self.lang.as_deref().unwrap_or("???"),
108            self.diagnosis,
109            self.sig,
110        )
111    }
112}
113
114#[derive(Debug)]
115pub struct WebServerQualityMatched {
116    pub web_server: Option<WebServer>,
117    pub quality: MatchQualityType,
118}
119
120/// Represents a web server.
121///
122/// This struct contains the name, family, variant, and kind of browser.
123/// Examples:
124/// - name: "", family: "apache", variant: "2.x", kind: Type::Specified
125/// - name: "", family: "nginx", variant: "1.x", kind: Type::Specified
126#[derive(Debug)]
127pub struct WebServer {
128    pub name: String,
129    pub family: Option<String>,
130    pub variant: Option<String>,
131    pub kind: Type,
132}
133
134impl From<&Label> for WebServer {
135    fn from(label: &Label) -> Self {
136        WebServer {
137            name: label.name.clone(),
138            family: label.class.clone(),
139            variant: label.flavor.clone(),
140            kind: label.ty.clone(),
141        }
142    }
143}
144
145/// Holds information derived from analyzing HTTP response headers.
146///
147/// This structure contains details about the server, the detected application
148/// (if any), diagnostic parameters related to HTTP behavior, and the raw HTTP signature.
149#[derive(Debug)]
150pub struct HttpResponseOutput {
151    /// The source IP address and port of the server sending the response.
152    pub source: IpPort,
153    /// The destination IP address and port of the client receiving the response.
154    pub destination: IpPort,
155    /// Diagnostic information about potential HTTP specification violations or common practices.
156    pub diagnosis: HttpDiagnosis,
157    /// The label identifying the likely server application (e.g., Apache, Nginx) and the quality.
158    pub web_server_matched: WebServerQualityMatched,
159    /// The raw signature representing the HTTP headers and their order.
160    pub sig: ObservableHttpResponse,
161}
162
163impl fmt::Display for HttpResponseOutput {
164    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
165        write!(
166            f,
167            "[HTTP Response] {}:{} → {}:{}\n\
168              Server:  {}\n\
169              Params:  {}\n\
170              Sig:     {}\n",
171            self.source.ip,
172            self.source.port,
173            self.destination.ip,
174            self.destination.port,
175            self.web_server_matched
176                .web_server
177                .as_ref()
178                .map_or("???".to_string(), |web_server| {
179                    if !web_server.name.is_empty() {
180                        web_server.name.clone()
181                    } else {
182                        format!(
183                            "{}:{}",
184                            web_server.family.as_deref().unwrap_or("???"),
185                            web_server.variant.as_deref().unwrap_or("???")
186                        )
187                    }
188                }),
189            self.diagnosis,
190            self.sig,
191        )
192    }
193}